diff --git a/.dependabot/config.yml b/.dependabot/config.yml index e73f5e011..3f0b2af5c 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -1,8 +1,19 @@ version: 1 update_configs: + - package_manager: "java:gradle" directory: "/jbake-core" - update_schedule: "daily" + update_schedule: "monthly" + - package_manager: "java:gradle" directory: "/jbake-dist" - update_schedule: "daily" + update_schedule: "monthly" + + - package_manager: "java:maven" + directory: "/jbake-core" + update_schedule: "monthly" + + - package_manager: "java:maven" + directory: "/jbake-dist" + update_schedule: "monthly" + diff --git a/.editorconfig b/.editorconfig index fcc2c20cc..7e0e491d0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,4 +6,7 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space -indent_size = 4 \ No newline at end of file +indent_size = 4 + +[AsciidocParserTest.kt] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a12a50eb..f01c2e791 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,36 +13,36 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v5 with: - java-version: 11 + java-version: 17 distribution: 'zulu' - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-cache-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }} restore-keys: | ${{ runner.os }}-gradle- - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradlew') }} restore-keys: | ${{ runner.os }}-gradlew- - - name: Build-win + - name: Build-windows if: runner.os == 'Windows' shell: cmd run: gradlew.bat -Dfile.encoding=UTF-8 build -S - - name: Build-nix + - name: Build-linux if: runner.os != 'Windows' run: ./gradlew build -S - name: Upload Reports - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 if: failure() with: name: reports-${{ runner.os }} diff --git a/.github/workflows/early-access.yml b/.github/workflows/early-access.yml index 23367b2a7..3e613ebc4 100644 --- a/.github/workflows/early-access.yml +++ b/.github/workflows/early-access.yml @@ -12,23 +12,23 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'zulu' - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-cache-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }} restore-keys: | ${{ runner.os }}-gradle- - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradlew') }} @@ -49,7 +49,7 @@ jobs: - name: JReleaser output if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: jreleaser-logs path: | diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml new file mode 100644 index 000000000..6a7d25580 --- /dev/null +++ b/.github/workflows/maven-build.yml @@ -0,0 +1,47 @@ +name: Maven Tests + +on: + push: + branches: [ "*" ] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2- + + - name: Run Maven tests + run: mvn -B clean install + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v4 + with: + path: | + **/target/surefire-reports/* + **/target/failsafe-reports/* + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2.21.0 + with: + check_name: 'Maven Tests' + files: | + **/target/surefire-reports/*.xml + **/target/failsafe-reports/*.xml diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 000000000..15e8f217c --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,42 @@ +#-------------------------------------------------------------------------------# +# Discover all capabilities of Qodana in our documentation # +# https://www.jetbrains.com/help/qodana/about-qodana.html # +#-------------------------------------------------------------------------------# + +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - kotlin-upgraded + +jobs: + qodana: + if: false # Disabled + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.2 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + project.open.type: Gradle + with: + # When pr-mode is set to true, Qodana analyzes only the files that have been changed + pr-mode: false + use-caches: true + post-pr-comment: true + use-annotations: true + # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job + upload-result: false + # Quick-fixes available in Ultimate and Ultimate Plus plans + push-fixes: 'none' diff --git a/.gitignore b/.gitignore index 82ab65eb3..510f3c624 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .project /.settings /target +**/target /bin /dist /.idea @@ -12,3 +13,7 @@ build/ .gradle .gradletasknamecache +/docs/kotlin-migration-line-analysis/* +dependency-reduced-pom.xml +pom.xml.versionsBackup +.flattened-pom.xml diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 000000000..529efdc57 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,14 @@ + + + + org.apache.maven.extensionsmaven-build-cache-extension1.2.1 + + + fr.jcgay.mavenmaven-profiler3.3 + + + + diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 000000000..cd7406e7c --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1,10 @@ +--add-opens=java.base/java.lang=ALL-UNNAMED +--add-opens=java.base/java.io=ALL-UNNAMED +-XX:-UseParallelGC +-XX:+UseStringDeduplication +-XX:+UseLargePages +-XX:+OptimizeStringConcat +-XX:G1HeapWastePercent=20 + +-XX:+UnlockExperimentalVMOptions +-XX:+EnableJVMCI diff --git a/.mvn/maven-build-cache-config.xml b/.mvn/maven-build-cache-config.xml new file mode 100644 index 000000000..1f27fc2b9 --- /dev/null +++ b/.mvn/maven-build-cache-config.xml @@ -0,0 +1,77 @@ + + + + + false + + 5 + + + + + + + + **/src/** + pom.xml + **/pom.xml + + + + **/target/** + **/.flattened-pom.xml + + + + + + + + + + + + + + install + deploy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.mvn/maven-version-rules.xml b/.mvn/maven-version-rules.xml new file mode 100644 index 000000000..de73a4c17 --- /dev/null +++ b/.mvn/maven-version-rules.xml @@ -0,0 +1,13 @@ + + + + + + .*[-_.](?i)(alpha|beta|rc|milestone|m[0-9]+|preview|pre|ea|cr|snapshot).* + + + diff --git a/BUILD.adoc b/BUILD.adoc index dc4f0ffea..41ad91d0c 100644 --- a/BUILD.adoc +++ b/BUILD.adoc @@ -1,64 +1,115 @@ +// suppress inspection "AsciiDocAttributeShouldBeDefined" for whole file = Test, Build and Deploy :toc: :gradle-home: http://gradle.org[Gradle] :gradle-userguide: https://docs.gradle.org/current/userguide/userguide.html[gradle userguide] :gradle-wrapper: https://docs.gradle.org/current/userguide/gradle_wrapper.html[gradle wrapper] +:maven-home: https://maven.apache.org/[Maven] +:maven-guide: https://maven.apache.org/guides/[maven guide] :jacoco-web: http://www.eclemma.org/jacoco/[jacoco] -:coveralls: https://coveralls.io/github/jbake-org/jbake[coveralls] :sdkman: http://sdkman.io[sdkman] +:kotlin: https://kotlinlang.org/[Kotlin] -...and other useful stuff you can do with the {gradle-home} build system. +...and other useful stuff you can do with the build systems. -The project uses {gradle-home} as the build system. -This is just a little collection of the common tasks you need to know to develop, build and deploy JBake. For more detailed information about gradle have a look at the {gradle-userguide}. +The project supports both {gradle-home} and {maven-home} as build systems. -To execute the tasks use the {gradle-wrapper}. That way you do not need to install - gradle for yourself and can be sure you are using the exact version everyone else is using to build JBake. +* **Gradle** - The original build system, using Gradle 9 +* **Maven** - Full Maven support with synchronized dependencies +* **Kotlin** - The codebase includes {kotlin} code with proper documentation generation -You can execute the build using one of the following commands from the root of the project: +This document covers common tasks for developing, building and deploying JBake. -* `./gradlew ` (on Unix-like platforms such as Linux and Mac OS X) +== Build Systems + +=== Using Gradle + +To execute Gradle tasks use the {gradle-wrapper}. This ensures you're using the exact Gradle version (9.x) configured for the project. +Commands from the root of the project: + +* `./gradlew ` (on Unix-like platforms such as Linux and Mac OS X) * `gradlew ` (on Windows using the gradlew.bat batch file) -To get an overview of all available tasks with a short description run `./gradlew tasks` +To get an overview of all available tasks: `./gradlew tasks` + +=== Using Maven + +Maven support is fully configured. Maven will automatically use JDK 17 via toolchains. + +Commands from the root of the project: + +* `mvn ` - Maven will auto-detect JDK 17 from toolchains + +For detailed Maven instructions see link:docs/releasing/RELEASING.md[RELEASING.md] == Structure -There are 4 projects: +There are 5 modules (both Gradle and Maven): root aka. jbake-base:: - configures subprojects, jacoco execution aggregation and coveralls + - Parent project configuring submodules + - Gradle: jacoco execution aggregation + - Maven: dependency management, plugin management, release configuration jbake-core:: - - the core library. produces jbake-core-{version}.jar (`build/libs`) - - publishes to bintray maven repository jbake-core + - The core library with Java and Kotlin code + - Produces `jbake-core-{version}.jar` + - Gradle: `build/libs` + - Maven: `target/` + - Documentation generated with Dokka for Kotlin code + +jbake-dist:: + - Bundles the CLI distribution + - Creates binary packages and Docker images + - Gradle: `build/distributions` + - Maven: `target/` jbake-maven-plugin:: - - the JBake maven plugin, build by Gradle too + - The JBake Maven plugin + - Built by both Gradle and Maven -jbake-dist:: - - bundles the cli to an distribution (`build/distribution`) - - publishes to bintray binary repository jbake - - publish to sdkman +jbake-e2e-tests:: + - End-to-end integration tests + - Tests Docker container functionality + - Not published to Maven Central -If you want to run a task in a specific project from root run `./gradlew :jbake-core:test` for example. +=== Running tasks in specific modules + +**Gradle:** +---- +./gradlew :jbake-core:test +---- + +**Maven:** +---- +mvn test -pl jbake-core +---- == Test -=== run the tests +=== Run the tests + While developing this is the most common task you should execute. +**Gradle:** ---- ./gradlew test ---- -This task compiles and executes all tests within `src/test` and produces a report afterwards. +**Maven:** +---- +mvn test +---- + +This compiles and executes all tests within `src/test` and produces a report afterwards. + +**Gradle reports:** `jbake-core/build/reports/tests/test/index.html` -You can find the report at `jbake-core/build/reports/tests/test/index.html` and can view it with your browser. -This is very useful if something went wrong. -You find the full stacktrace and output there. +**Maven reports:** `jbake-core/target/surefire-reports/` + +You can view these reports with your browser to see full stacktraces and output when tests fail. === know what's going on @@ -99,64 +150,67 @@ It executes the produced application, initializes a jbake project for each suppo The `check` task depends on the `smokeTest` task and is part of the travis CI execution. You can find the report at `jbake-dist/build/reports/tests/smokeTest/` -=== code coverage +=== Code coverage -To generate a nice code coverage report run the following task. +Generate code coverage reports with {jacoco-web}. +**Gradle (aggregated):** ---- ./gradlew jacocoRootReport ---- +Report: `build/reports/jacoco/jacocoRootReport/html` -It compiles your code, execute your tests, collect data and generate a report with {jacoco-web}. It produces XML and html reports. The xml file is used to trigger the {coveralls} service with the `coveralls` task. - -The reports can be found at `build/reports/jacoco/jacocoRootReport/html`. - -[NOTE] -==== -This is an aggregation of all project modules. - -You can generate coverage reports for each module with `./gradlew jacocoTestReport` -or for a particular module `./gradlew :jbake-core:jacocoTestReport`. +**Gradle (per module):** +---- +./gradlew :jbake-core:jacocoTestReport +---- +Report: `/build/reports/jacoco/test/html/` -The report can be found at `/build/reports/jacoco/test/html/` -==== +**Maven (per module):** +---- +mvn test +---- +Reports are generated automatically in `/target/jacoco/` plugin:: https://docs.gradle.org/current/userguide/jacoco_plugin.html -// TODO: write something about smokeTests and check == Build -=== run the build +=== Run the build The `build` task assembles and tests the project. +**Gradle:** ---- ./gradlew build ---- -It clones the example projects from github, creates zip files, generates start scripts for *NIX and Windows, bundles a distribution package, signs archives (if signing is configured properly), generates javadocs, assemble the packages and runs checks. - +**Maven:** ---- -./gradlew build - -BUILD SUCCESSFUL in 47s -28 actionable tasks: 10 executed, 18 up-to-date +mvn clean package ---- -If successful you can find everything in the `jbake-dist/build` directory. -The distribution package can be found at `jbake-dist/build/distributions` and is called `jbake-{version}-bin.zip` +This compiles code, runs tests, generates javadocs, creates distribution packages, and runs checks. + +**Gradle output:** `jbake-dist/build/distributions/jbake-{version}-bin.zip` -=== install local +**Maven output:** `jbake-dist/target/jbake-{version}-bin.zip` -You can install the distribution locally. +=== Install local +**Gradle:** ---- ./gradlew installDist ---- +Output: `jbake-dist/build/install/jbake` -The distribution can be found in an exploded directory called `jbake-dist/build/install/jbake`. +**Maven:** +---- +mvn clean install +---- +Installs to local Maven repository: `~/.m2/repository/ch/zizka/jbake/` -NOTE: This task does not run checks. It just compiles and bundles the distribution. +NOTE: These tasks do not run all checks. They just compile and bundle the distribution. plugin:: https://docs.gradle.org/current/userguide/application_plugin.html @@ -164,28 +218,53 @@ plugin:: https://docs.gradle.org/current/userguide/application_plugin.html WARNING: Never add credentials to the repository -=== github release +=== Releasing to Maven Central + +The project is published to Maven Central under the `ch.zizka.jbake` groupId. + +For complete release instructions including GPG key setup, credentials configuration, and troubleshooting, see: + +**link:docs/releasing/RELEASING.md[RELEASING.md]** + +**Quick release command (Maven):** +---- +mvn clean deploy -Prelease +---- + +This will: + +* Build all modules +* Generate sources and javadoc JARs (using Dokka for Kotlin code) +* Sign all artifacts with GPG +* Upload to Maven Central Portal +* Automatically publish to Maven Central (appears in 15-30 minutes) + +**Prerequisites:** + +* GPG key uploaded to keyservers +* Maven Central Portal credentials in `~/.m2/settings.xml` +* Java 17 (automatically selected via Maven toolchains) -Bump desired project version in the projects `gradle.properties` file. +=== GitHub release -Like +Bump desired project version in `gradle.properties`: ---- -version = 2.7.0 +version = 100.0.0 ---- -Commit and push to origin at github. +Commit and push to GitHub, then: ---- ./gradlew signArchives ./gradlew githubRelease ---- -The task will create a tag for you and create a release. Additionaly it uploads the binary distribution and the corresponding signature to the release. +This creates a tag, creates a GitHub release, and uploads the binary distribution with signature. [NOTE] ==== -You need to add some properties to your local gradle.properties file (_~/.gradle/gradle.properties_) +Add to your local `~/.gradle/gradle.properties`: ---- github.token= @@ -193,35 +272,32 @@ github.release.owner=jbake-org github.release.repo=jbake ---- -It's also possible to dry-run this task. Execute `export GITHUB_RELEASE_DRY_RUN=true` in your terminal. +For dry-run: `export GITHUB_RELEASE_DRY_RUN=true` ==== plugin:: https://github.com/BreadMoirai/github-release-gradle-plugin -=== publish to nexus sonatype +=== Publish with Gradle (alternative) -You can publish to nexus with +You can also publish to Maven Central using Gradle: ---- ./gradlew publishToSonatype ---- -The task will create a staging repository. You need to close and publish it manually. -You can automate this process with the other tasks like `closeSonatypeStagingRepository` and `closeAndReleaseSonatypeStagingRepository`. - -For more information see: +For more information: * https://github.com/gradle-nexus/publish-plugin -* https://central.sonatype.org/pages/ossrh-guide.html +* https://central.sonatype.org/ -You need to add two properties to your local gradle.properties file (_~/.gradle/gradle.properties_). +Add to `~/.gradle/gradle.properties`: sonatypeUsername=username sonatypePassword=secret plugin:: https://plugins.gradle.org/plugin/io.github.gradle-nexus.publish-plugin -=== publish to sdkman +=== Publish to SDKMAN To release, set to default and announce a new candidate of JBake to {sdkman} run @@ -234,7 +310,38 @@ Add the following properties to your local _gradle.properties_ file (_~/.gradle/ plugin:: https://plugins.gradle.org/plugin/io.sdkman.vendors -=== signing +==== Publish to SDKMAN with Maven + +To publish to SDKMAN using Maven, activate the `sdkman` profile: + + mvn clean deploy -Psdkman + +Add the following properties to your local _~/.m2/settings.xml_: + +[source,xml] +---- + + + + sdkman + + your-consumer-key + your-consumer-token + + + + +---- + +Or pass them on the command line: + + mvn clean deploy -Psdkman -Dsdkman.consumer.key=your-key -Dsdkman.consumer.token=your-token + +This will automatically announce the new JBake version to SDKMAN during the Maven deploy phase. + +plugin:: https://github.com/sdkman/sdkman-maven-plugin + +=== Signing To enable code signing you need to add some more properties to your local _gradle.properties_ file (_~/.gradle/gradle.properties_): @@ -248,7 +355,7 @@ plugin:: https://docs.gradle.org/current/userguide/signing_plugin.html == Other useful tasks -=== check code convention violations +=== Check code convention violations The Checkstyle Plugin is configured to use our code conventions defined in `config/checkstyle/checkstyle.xml`. @@ -257,59 +364,25 @@ A report can be found at jbake-core/build/reports/checkstyle/. plugin:: https://docs.gradle.org/current/userguide/checkstyle_plugin.html -=== keep the dependencies up-to-date - -It's sometimes hard to keep track of the latest versions for your dependencies. -Fear not. +=== Keep the dependencies up-to-date +**Gradle:** ---- ./gradlew dependencyUpdates -:dependencyUpdates -Download https://jcenter.bintray.com/org/assertj/assertj-core/3.8.0/assertj-core-3.8.0.pom - ------------------------------------------------------------- -: Project Dependency Updates (report to plain text file) ------------------------------------------------------------- - -The following dependencies are using the latest milestone version: - - args4j:args4j:2.33 - - org.asciidoctor:asciidoctorj:1.5.5 - - commons-configuration:commons-configuration:1.10 - - commons-io:commons-io:2.5 - - org.apache.commons:commons-lang3:3.5 - - org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1 - - org.freemarker:freemarker:2.3.26-incubating - - com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3 - - com.github.ben-manes:gradle-versions-plugin:0.14.0 - - org.codehaus.groovy:groovy:2.4.11 - - org.codehaus.groovy:groovy-templates:2.4.11 - - de.neuland-bfi:jade4j:1.2.5 - - org.eclipse.jetty:jetty-server:9.4.5.v20170502 - - com.googlecode.json-simple:json-simple:1.1.1 - - org.slf4j:jul-to-slf4j:1.7.25 - - junit:junit:4.12 - - ch.qos.logback:logback-classic:1.2.3 - - ch.qos.logback:logback-core:1.2.3 - - org.mockito:mockito-core:2.8.9 - - com.orientechnologies:orientdb-graphdb:2.2.20 - - org.slf4j:slf4j-api:1.7.25 - - org.thymeleaf:thymeleaf:3.0.6.RELEASE - -The following dependencies exceed the version found at the milestone revision level: - - org.pegdown:pegdown [1.6.0 <- 1.5.0] - -The following dependencies have later milestone versions: - - org.assertj:assertj-core [3.7.0 -> 3.8.0] - - org.apache.commons:commons-vfs2 [2.1 -> 2.1.1744488.1] - -Failed to determine the latest version for the following dependencies (use --info for details): - - gradle.plugin.io.sdkman:gradle-sdkvendor-plugin - -Generated report file build/dependencyUpdates/report.txt - -BUILD SUCCESSFUL - -Total time: 6.721 secs ----- - -plugin:: https://plugins.gradle.org/plugin/com.github.ben-manes.versions +---- + +Generates a report showing available dependency updates at `build/dependencyUpdates/report.txt` + +**Maven:** +---- +mvn versions:display-dependency-updates +mvn versions:display-plugin-updates +---- + +The Maven versions plugin is configured to exclude pre-release versions (alpha, beta, RC, milestone, etc.) via `maven-version-rules.xml`. + +Both Gradle and Maven dependency versions are synchronized between `gradle.properties` and `pom.xml`. + +Gradle plugin:: https://plugins.gradle.org/plugin/com.github.ben-manes.versions + +Maven plugin:: https://www.mojohaus.org/versions/versions-maven-plugin/ diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index e3ba82bd3..09e172acb 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -1,24 +1,24 @@ -= Contributing += Contributing to JBake // settings: :idprefix: :idseparator: - -:source-language: java +:source-language: kotlin :language: {source-language} ifdef::env-github,env-browser[:outfilesuffix: .adoc] // URIs: -:uri-repo: https://github.com/jbake-org/jbake -:uri-help-base: https://help.github.com/articles +:uri-repo: https://github.com/OndraZizka/jbake +:uri-help-base: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests :uri-issues: {uri-repo}/issues -:uri-fork-help: {uri-help-base}/fork-a-repo -:uri-branch-help: {uri-fork-help}#create-branches -:uri-pr-help: {uri-help-base}/using-pull-requests +:uri-fork-help: {uri-help-base}/working-with-forks/fork-a-repo +:uri-branch-help: {uri-help-base}/working-with-forks/fork-a-repo#creating-a-branch-to-work-on +:uri-pr-help: {uri-help-base}/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request :uri-gist: https://gist.github.com -:uri-hamcrest: http://hamcrest.org/JavaHamcrest/ -:uri-assertj: http://joel-costigliola.github.io/assertj/ +:uri-kotest: https://kotest.io/ :uri-license: LICENSE +:uri-build: BUILD.adoc -First off thanks for your interest in improving JBake! We appreciate you taking the time to contribute to JBake and to -ensure that your contribution is easy to review and process we kindly ask that you follow the guidance outlined below. +Thank you for your interest in improving JBake! We appreciate you taking the time to contribute and to +ensure that your contribution is easy to review and process, please follow the guidance outlined below. == License Agreement @@ -38,22 +38,27 @@ content on which triggers the bad behaviour in JBake. == Raising a Pull Request -Prior to starting work on a Pull Request please post a message on the developers mailing list outlining the new feature -or bug you are planning to work on. You can then be advised if anyone else is already working on the same topic or -if there is any pertinent information that you should be made aware of such as upcoming structural or API changes. +Before starting work on a Pull Request, please create an issue or check existing {uri-issues}[issues] to see if someone +is already working on the same topic. This helps avoid duplicate work and allows us to provide guidance on upcoming +structural or API changes. -Once you're ready to start work on your pull request follow the steps outlined below. +Once you're ready to start work on your pull request: -. {uri-fork-help}[Fork the repository]. -. {uri-branch-help}[Create a topic branch]. -. Add tests for your unimplemented feature or bug fix. (See <>) -. Implement your feature or bug fix. -. Run `./gradlew test` to run the tests. If your tests fail, return to step 4. -. Add, commit, and push your changes. -. {uri-pr-help}[Submit a pull request]. +. {uri-fork-help}[Fork the repository] +. {uri-branch-help}[Create a topic branch] +. Add tests for your feature or bug fix (see <>) +. Implement your feature or bug fix +. Run tests: + * Gradle: `./gradlew test` + * Maven: `mvn test` +. If tests fail, return to step 4 +. Add, commit, and push your changes +. {uri-pr-help}[Submit a pull request] -For ideas about how to use pull requests, see the post -http://blog.quickpeople.co.uk/2013/07/10/useful-github-patterns[Useful GitHub Patterns]. +=== Build Systems + +JBake supports both Gradle and Maven. Changes to dependencies or build configuration should be synchronized between both +systems. See link:{uri-build}[BUILD.adoc] for detailed build instructions. //// uncomment when code style & prefs have been defined @@ -65,36 +70,70 @@ can import this into their IDE. === Writing and Executing Tests -Tests and their resources reside within `/src/test` and should be written using the JUnit framework, you also have the -following testing libraries available to you when writing your tests: +Tests and their resources reside within `/src/test` directory. JBake is written in Kotlin and uses modern Kotlin testing frameworks. -* {uri-hamcrest}[Hamcrest] -* {uri-assertj}[AssertJ] +==== Available Testing Libraries -Any resources, such as templates, that your tests require should be placed within `/src/test/resources`. +* {uri-kotest}[Kotest] - Kotlin-native testing framework (preferred for new tests) +* MockK - Kotlin mocking library -To execute the tests from the command line you can execute the following command from the root folder in the repository: +For Kotlin tests, prefer Kotest as it provides idiomatic Kotlin testing support with expressive DSL. - $ ./gradlew test +==== Test Resources -You will be able to run the unit tests in most IDE's such as Eclipse as well. +Place any test resources (templates, sample content, etc.) in `/src/test/resources`. -If you would like to utilise another testing library in your test classes, then please mention this and your reasons -for use when posting on the developers mailing list about your planned contribution. +==== Running Tests -If you would like to use a different testing framework instead of JUnit you may volunteer to convert the existing test -suite over to the new framework, we'd prefer not to have multiple test suites in different frameworks. +**Gradle:** +---- +./gradlew test +---- -//// -this next section is messy, documentation should be included with the project, a copy should exist in the web site -repo but not only there -=== Documentation +**Maven:** +---- +mvn test +---- -Some pull requests may alter the existing behaviour of or add a new feature to JBake, in this scenario please -review the JBake documentation and make... -//// +**IDE Support:** + +Tests can be run directly in modern IDEs (IntelliJ IDEA, Eclipse, VS Code). + +==== Guidelines + +* Write tests for new features and bug fixes +* Ensure tests pass before submitting a PR +* Use descriptive test names in Kotlin style (backticks for readable names) +* Keep tests focused and isolated +* Prefer Kotest for new tests - it provides better Kotlin integration +* Use MockK for mocking Kotlin classes === Supported Java Versions -At present JBake supports Java 8 and above so any contributions shouldn't use Java 9+ syntax, please see -the {uri-issues}[roadmap] for when support for later Java versions is planned. +JBake requires **Java 17** for building and running. The project uses Maven toolchains to automatically select the correct +JDK version, so you need to have JDK 17 installed. + +Maven will automatically use JDK 17 if you have `~/.m2/toolchains.xml` configured (see link:{uri-build}[BUILD.adoc]). + +=== Code Language + +The project is written entirely in **Kotlin**. + +When contributing: + +* All new code should be written in Kotlin +* Target Kotlin 2.2+ features +* Follow Kotlin coding conventions and idioms +* Use Kotlin's null-safety features +* Prefer immutable data structures where possible +* Follow existing code patterns in the module you're modifying + +=== Documentation + +If your pull request alters existing behavior or adds a new feature: + +* Update relevant documentation in the `docs/` directory +* Update README.asciidoc if adding user-facing features +* Add or update KDoc comments for public APIs (Kotlin's documentation format) +* Consider adding examples in `test-data/` for new features +* Documentation is generated using Dokka for Kotlin code diff --git a/Dockerfile b/Dockerfile index e17527c5c..b54cde8ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM gradle:6.8-jdk11 as builder +FROM gradle:9.2.1-jdk17 AS builder LABEL maintainer="https://jbake.org/community/team.html" @@ -14,7 +14,7 @@ RUN set -o errexit -o nounset \ && cp -r jbake-dist/build/install/jbake/* $JBAKE_HOME \ && rm -r ~/.gradle /usr/src/jbake -FROM eclipse-temurin:11-jre-alpine +FROM eclipse-temurin:17-jre-alpine ENV JBAKE_USER=jbake ENV JBAKE_HOME=/opt/jbake diff --git a/README.asciidoc b/README.asciidoc index 5e5383d65..bbb8e3d1b 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -1,23 +1,27 @@ = JBake -Jonathan Bullock -2021-04-13 +Ondrej Zizka, Jonathan Bullock +2025-12-01 :idprefix: http://jbake.org[JBake] is a Java based open source static site/blog generator for developers. -image:https://img.shields.io/github/v/release/jbake-org/jbake?label=download&sort=semver["GitHub Release Download", link="https://github.com/jbake-org/jbake/releases/latest"] -image:https://img.shields.io/maven-central/v/org.jbake/jbake-core.svg["Maven Download", link="http://jbake.org/download.html#maven"] -image:https://img.shields.io/homebrew/v/jbake.svg["Homebrew Download", link="http://jbake.org/download.html#homebrew"] +NOTE: This is a fork maintained by Ondrej Zizka with modernizations including Kotlin 2.2, Gradle 9, JDK 17+, and Maven build support. -image:https://img.shields.io/travis/com/jbake-org/jbake/master.svg["Build Status", link="https://travis-ci.com/github/jbake-org/jbake"] -image:https://ci.appveyor.com/api/projects/status/2q7hvg03wsjx953b?svg=true["Appveyor Status", link="https://ci.appveyor.com/project/jbake-org/jbake"] -image:https://img.shields.io/coveralls/jbake-org/jbake/master.svg["Coverage Status", link="https://coveralls.io/r/jbake-org/jbake"] +image:https://img.shields.io/github/v/release/OndraZizka/jbake?label=download&sort=semver["GitHub Release Download", link="https://github.com/OndraZizka/jbake/releases/latest"] +image:https://img.shields.io/maven-central/v/ch.zizka.jbake/jbake-core.svg["Maven Central", link="https://central.sonatype.com/artifact/ch.zizka.jbake/jbake-core"] +image:https://github.com/jbake-org/jbake/actions/workflows/maven-build.yml/badge.svg["Maven Tests", link="https://github.com/jbake-org/jbake/actions/workflows/maven-build.yml"] -image:https://badges.gitter.im/jbake-org/jbake.svg["Gitter Chat", link="https://gitter.im/jbake-org/jbake"] +*Version:* 5.0.0-rc1 | *Group ID:* `ch.zizka.jbake` == Documentation -Full documentation is available on http://jbake.org/docs/[jbake.org]. +Original documentation is available on http://jbake.org/docs/[jbake.org]. + +Additional documentation for this fork: + +* link:BUILD.adoc[Build Guide] - Information on building with Gradle or Maven +* link:docs/releasing/RELEASING.md[Release Guide] - Instructions for publishing to Maven Central +* link:CONTRIBUTING.asciidoc[Contributing Guide] == Contributing @@ -56,9 +60,8 @@ Talk to other users of JBake on the forum: == Docker Image -The image uses the official https://hub.docker.com/r/adoptopenjdk/openjdk11/[adoptopenjdk/openjdk11:alpine] image -for building a distribution of JBake and -https://hub.docker.com/r/adoptopenjdk/openjdk11/[adoptopenjdk/openjdk11:alpine-jre] for runtime. +The image uses https://hub.docker.com/_/gradle[gradle:9.2.1-jdk17] for building a distribution of JBake and +https://hub.docker.com/_/eclipse-temurin[eclipse-temurin:17-jre-alpine] for runtime. === Build @@ -68,44 +71,33 @@ To build the Docker image: $ docker build -t jbake/jbake:latest . ---- -=== Usage - -To execute JBake via Docker run this from project directory: +W ----- -$ docker run --rm -u jbake -v "$PWD":/mnt/site jbake/jbake:latest ----- -This command will execute using the jbake user to avoid running as root and will mount the current working directory as `/mnt/site` -in the Docker container where JBake is expecting your project files to be. By default the Docker image will execute a bake `-b` only. +== Build System -If you want to bake and serve your project using the Docker image then you'll need to override the default command: +This project supports both http://gradle.org[Gradle] 9+ and https://maven.apache.org[Maven] as build systems. ----- -$ docker run --rm -u jbake -v "$PWD":/mnt/site -p 8820:8820 jbake/jbake:latest -b -s ----- +=== Building with Gradle -This command will also expose port 8820 from the container, you'll also need to set the following option in your `jbake.properties` file: +To build the JBake distribution ZIP file: ---- -server.hostname=0.0.0.0 +$ ./gradlew distZip ---- -NOTE: Docker image timezone is _UTC_. This may affect the date and time expected in output content. To set different timezone, add `TZ` environment variable and set value to required https://en.wikipedia.org/wiki/List_of_tz_database_time_zones[timezone^]. Example - `docker run --rm -u jbake -e TZ=America/New_York -v "$PWD":/mnt/site jbake/jbake:latest` +This will build a ZIP file in the `jbake-dist/build/distributions` folder.W +=== Building with Maven -== Build System - -The project uses http://gradle.org[Gradle] 4.9+ as the build system. -To build the JBake distribution ZIP file execute the following command from the root of the repo: +To build the project with Maven: ---- -$ ./gradlew distZip +$ export JAVA_HOME=/path/to/jdk-17 +$ mvn clean install ---- -This will build a ZIP file in the `/build/distributions` folder. - -For more information see link:BUILD.adoc[Test, Build and Deploy] +For more information see link:BUILD.adoc[Build Guide] == Coding conventions @@ -142,17 +134,66 @@ Pick the project checkstyle file `config/checkstyle/checkstyle.xml` == Tools & Libraries Used -* http://commons.apache.org/[Apache Commons IO, Configuration, Lang & Logging] -* http://args4j.kohsuke.org/[Args4j] -* http://asciidoctor.org/[AsciidoctorJ] -* http://freemarker.org/[Freemarker] -* http://gradle.org[Gradle] -* http://groovy-lang.org/[Groovy] -* http://junit.org/[JUnit] -* https://github.com/vsch/flexmark-java[Flexmark] -* http://www.eclipse.org/jetty/[Jetty Server] -* http://www.orientdb.org/[OrientDB] +=== Platform & Core + +* https://kotlinlang.org/[Kotlin 2.2] - Primary programming language +* https://www.oracle.com/java/technologies/downloads/#java17[JDK 17+] - Java Development Kit +* http://commons.apache.org/proper/commons-configuration/[Apache Commons Configuration] - Configuration management +* https://logback.qos.ch/[Logback] & https://www.slf4j.org/[SLF4J] - Logging framework +* http://args4j.kohsuke.org/[Args4j] - Command-line argument parsing +* http://www.eclipse.org/jetty/[Jetty Server] - Embedded web server +* http://www.orientdb.org/[OrientDB] - Document database for content storage + +=== Templating Engines + +* http://freemarker.org/[Freemarker] - Template engine +* http://groovy-lang.org/[Groovy] - Groovy templates support +* https://www.thymeleaf.org/[Thymeleaf] - Modern server-side Java template engine +* https://github.com/jknack/handlebars.java[Handlebars.java] - Handlebars template engine + +=== Content Parsing & Processing + +* http://asciidoctor.org/[AsciidoctorJ] - AsciiDoc processor +* https://github.com/vsch/flexmark-java[Flexmark] - Markdown parser and processor +* https://commonmark.org/[CommonMark] - Markdown specification implementation + +=== Build Systems + +* https://maven.apache.org/[Maven] - Primary build system for multi-module projects +* http://gradle.org[Gradle 9] - Alternative build system with Kotlin DSL support +* https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle Wrapper] - Version-locked Gradle distribution + +=== Testing + +* https://kotest.io/[Kotest] - Kotlin testing frameworkA +* https://mockk.io/[MockK] - Mocking library for Kotlin +* https://www.testcontainers.org/[Testcontainers] - Docker-based integration testing == Copyright & License Licensed under the MIT License, see the link:LICENSE[LICENSE] file for details. + +== TODOs + +* Update documentation to reflect changes in this fork +* Add more integration tests +* Rename to avoid confusion with the original project +* Publish to SDKMAN +* Set up a website for this fork +* Check if the maven plugin works +* Rename jbake-base to jbake-root +* Refactor the Map-backed models to use the reified inline trick for type-safe accessors. +* Solve the shader warnings - duplicate classes between dependencies. +* Split jbake-dist into one module per database. + * Or at least make Neo4j optional. +* Distribute also jbake-core for those who use it as a library. +* Add GitHub Actions for publishing to Maven Central. +* Add code coverage reporting. +* Logging: + * Add an option `-l` / `--log-level` to enable logging / set the logging level, and enable the logger *programatically* if this option is set. + * Also, if possible with the used argj library, add -v, -vv, -vvv, with the same effect as setting WARN, INFO, DEBUG levels. + * Lastly, add `--log-config=` which will point to a logback config file file relative to the user.dir, and apply it to LogBack's runtime config., +* Review all unchecked casts - @Suppress("UNCHECKED_CAST") +* What the heck is `temurin-17.0.17!/jdk.hotspot.agent/sun/jvm/hotspot/HelloWorld.class` ?! +* Create some type conversion layer between the Databases and the rendering? + * E.g., DB has OffsetDateTime (I want it that way), but the templates want Date. diff --git a/ROADMAP.txt b/ROADMAP.txt deleted file mode 100644 index 026d6a1d9..000000000 --- a/ROADMAP.txt +++ /dev/null @@ -1 +0,0 @@ -See https://github.com/jbake-org/jbake/issues instead diff --git a/build.gradle b/build.gradle index 9a3787aa2..9e8a6422c 100644 --- a/build.gradle +++ b/build.gradle @@ -7,25 +7,28 @@ plugins { id 'org.jreleaser' version "$jreleaserVersion" apply false id "eclipse" id "idea" + id 'org.jetbrains.kotlin.jvm' version "$kotlinVersion" apply false + id 'io.kotest' version "$kotestVersion" apply false + id 'java-platform' } + def buildTimeAndDate = grgit.head().dateTime -// common variables ext { - isTravis = (System.getenv("TRAVIS") == "true") - isTravisPullRequest = (System.getenv("TRAVIS_PULL_REQUEST")) != "false" - pullRequestId = System.getenv("TRAVIS_PULL_REQUEST") - pullRequestBranch = System.getenv("TRAVIS_PULL_REQUEST_BRANCH") - hasGithub = System.getenv("GITHUBTOKEN") && System.getenv("GITHUBREPO") - hasSonar = System.getenv("SONARORG") && System.getenv("SONARLOGIN") - sonarDefaultURL = "https://sonarcloud.io" - sonarDefaultProjectKey = "org.jbake:jbake-base:jbake-core" - sonarURL = System.getenv("SONARHOST") ?: sonarDefaultURL - sonarProjectKey = System.getenv("SONARPROJECTKEY") ?: sonarDefaultProjectKey - buildDate = buildTimeAndDate.format(DateTimeFormatter.ofPattern('yyyy-MM-dd')) - buildTime = buildTimeAndDate.format(DateTimeFormatter.ofPattern('HH:mm:ss.SSSZ')) - isReleaseVersion = !version.endsWith("SNAPSHOT") + isTravis = System.getenv("TRAVIS") == "true" + isTravisPullRequest = System.getenv("TRAVIS_PULL_REQUEST") != "false" + pullRequestId = System.getenv("TRAVIS_PULL_REQUEST") + pullRequestBranch = System.getenv("TRAVIS_PULL_REQUEST_BRANCH") + hasGithub = System.getenv("GITHUBTOKEN") && System.getenv("GITHUBREPO") + hasSonar = System.getenv("SONARORG") && System.getenv("SONARLOGIN") + sonarDefaultURL = "https://sonarcloud.io" + sonarDefaultProjectKey = "org.jbake:jbake-base:jbake-core" + sonarURL = System.getenv("SONARHOST") ?: sonarDefaultURL + sonarProjectKey = System.getenv("SONARPROJECTKEY") ?: sonarDefaultProjectKey + buildDate = buildTimeAndDate.format(DateTimeFormatter.ofPattern('yyyy-MM-dd')) + buildTime = buildTimeAndDate.format(DateTimeFormatter.ofPattern('HH:mm:ss.SSSZ')) + isReleaseVersion = !version.endsWith("SNAPSHOT") } nexusPublishing { @@ -46,3 +49,22 @@ dependencyUpdates.resolutionStrategy { } } } + +subprojects { + //dependencies { + //constraints { + configurations.configureEach { + resolutionStrategy { + // OrientDB + force "org.graalvm.truffle:truffle-api:$graalVmVersion" + + // Pug4j + force "org.graalvm.sdk:graal-sdk:$graalVmVersion" + //runtime "org.graalvm.js:js:$graalVmVersion" + //runtime "org.graalvm.regex:regex:$graalVmVersion" + force "org.graalvm.js:js-scriptengine:$graalVmVersion" + //runtime "org.graalvm.tools:profiler:$graalVmVersion" + //runtime "org.graalvm.tools:chromeinspector:$graalVmVersion" + } + } +} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 678405245..2adab9349 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -1,3 +1,29 @@ plugins { - id 'groovy-gradle-plugin' + id 'groovy' + id 'java-gradle-plugin' } + +repositories { + mavenCentral() +} + +dependencies { + implementation gradleApi() + implementation localGroovy() +} + +gradlePlugin { + plugins { + javaCommonConvention { + id = 'org.jbake.convention.java-common' + implementationClass = 'org.jbake.convention.JavaCommonConventionPlugin' + } + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + diff --git a/buildSrc/src/main/groovy/org/jbake/convention/JavaCommonConventionPlugin.groovy b/buildSrc/src/main/groovy/org/jbake/convention/JavaCommonConventionPlugin.groovy new file mode 100644 index 000000000..63ef24dad --- /dev/null +++ b/buildSrc/src/main/groovy/org/jbake/convention/JavaCommonConventionPlugin.groovy @@ -0,0 +1,78 @@ +package org.jbake.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.JavaVersion +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.api.tasks.testing.Test + +class JavaCommonConventionPlugin implements Plugin { + void apply(Project project) { + project.plugins.apply('java') + //project.plugins.apply('jacoco') + project.plugins.apply('checkstyle') + + project.repositories { + mavenCentral() + } + + project.dependencies { + implementation "org.slf4j:slf4j-api:${project.slf4jVersion}" + implementation "org.slf4j:jul-to-slf4j:${project.slf4jVersion}" + implementation "org.slf4j:jcl-over-slf4j:${project.slf4jVersion}" + implementation "ch.qos.logback:logback-classic:${project.logbackVersion}" + implementation "ch.qos.logback:logback-core:${project.logbackVersion}" + + testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit5Version}" + + testImplementation "org.itsallcode:junit5-system-extensions:${project.junit5SystemExtVersion}" + } + + project.tasks.withType(JavaCompile).configureEach { + // Skip smokeTest source set as it only contains Kotlin sources + if (it.name.contains('SmokeTest')) return + sourceCompatibility = "$javaVersion" + targetCompatibility = "$javaVersion" + } + + //set jvm for all Test tasks (like test and smokeTest) + project.tasks.withType(Test).configureEach { + def args = ['-Xms512m', '-Xmx3g', '-Dorientdb.installCustomFormatter=false=false', '-Djna.nosys=true'] + + /** + * jdk9 build is unable to determine the amount of MaxDirectMemorySize + * See https://pastebin.com/ECvQeHx0 + */ + if (JavaVersion.current().java9Compatible) { + args << '-XX:MaxDirectMemorySize=2g' + } + jvmArgs args + } + + project.tasks.register('javadocJar', Jar) { + archiveClassifier.set('javadoc') + from project.javadoc + } + + project.tasks.register('sourcesJar', Jar) { + archiveClassifier.set('sources') + from project.sourceSets.main.allSource + } + + project.tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true + } + + project.test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + } + } + } +} diff --git a/buildSrc/src/main/groovy/org/jbake/convention/org.jbake.convention.java-common.gradle b/buildSrc/src/main/groovy/org/jbake/convention/org.jbake.convention.java-common.gradle deleted file mode 100644 index 0b91690b6..000000000 --- a/buildSrc/src/main/groovy/org/jbake/convention/org.jbake.convention.java-common.gradle +++ /dev/null @@ -1,97 +0,0 @@ -plugins { - id 'java' - id 'jacoco' - id 'checkstyle' -} - -repositories { - mavenCentral() -} - -dependencies { - implementation "org.slf4j:slf4j-api:$slf4jVersion" - implementation "org.slf4j:jul-to-slf4j:$slf4jVersion" - implementation "org.slf4j:jcl-over-slf4j:$slf4jVersion" - implementation "ch.qos.logback:logback-classic:$logbackVersion" - implementation "ch.qos.logback:logback-core:$logbackVersion" - - testImplementation "org.junit-pioneer:junit-pioneer:$junitPioneer" - testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5Version" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version" - // compatibility for Junit 4 test - testCompileOnly "junit:junit:$junit4Version" - testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$junit5Version" - - testImplementation "org.assertj:assertj-core:$assertjCoreVersion" - testImplementation "org.mockito:mockito-core:$mockitoVersion" - testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion" - testImplementation "org.itsallcode:junit5-system-extensions:$junit5SystemExtVersion" -} - -tasks.withType(JavaCompile) { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 -} - -//set jvm for all Test tasks (like test and smokeTest) -tasks.withType(Test) { - - def args = ['-Xms512m', '-Xmx3g', '-Dorientdb.installCustomFormatter=false=false', '-Djna.nosys=true'] - - /** - * jdk9 build is unable to determine the amount of MaxDirectMemorySize - * See https://pastebin.com/ECvQeHx0 - */ - if (JavaVersion.current().java9Compatible) { - args << '-XX:MaxDirectMemorySize=2g' - } - jvmArgs args -} - -task javadocJar(type: Jar) { - archiveClassifier.set('javadoc') - from javadoc -} - -task sourcesJar(type: Jar) { - archiveClassifier.set('sources') - from sourceSets.main.allSource -} - -tasks.withType(AbstractArchiveTask) { - preserveFileTimestamps = false - reproducibleFileOrder = true -} - -test { - useJUnitPlatform() - - testLogging { - events "passed", "skipped", "failed" - exceptionFormat "full" - } - - jacoco { - excludes = ["**/*OrientSqlTokenManager*"] - } -} - -jacoco { - toolVersion = jacocoVersion -} - -jacocoTestReport { - reports { - xml.required.set true // coveralls plugin depends on xml format report - html.required.set true - } -} - -jacocoTestReport.dependsOn test - -tasks.withType(Checkstyle) { - reports { - xml.required.set false - html.required.set true - } -} diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 000000000..521727a15 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,237 @@ +# Contributing to JBake + +Thank you for your interest in contributing to JBake! This document provides guidelines and coding standards for the project. + +## Project Overview + +JBake is both a **tool** and a **library**: +- As a tool, it's used directly by end users to generate static sites +- As a library, it's consumed by Java projects as a dependency + +Therefore, all API classes must be **Java-friendly** and maintain backward compatibility. + +## Coding Style Guidelines + +### Kotlin-Java Interoperability + +Since JBake is used as a library by Java projects, always keep Java interoperability in mind: + +- Use `@JvmStatic` for static methods that should be accessible from Java +- Use `@JvmField` for properties that should be exposed as public fields in Java +- Use `@JvmOverloads` for constructors/methods with default parameters +- Avoid Kotlin-only features in public APIs that don't translate well to Java + +Example: +```kotlin +object PropertyList { + @JvmField val ARCHIVE_FILE: Property = Property("archive.file", "Output filename for archive file") + + @JvmStatic + fun getPropertyByKey(key: String): Property { + // ... + } +} +``` + +### Conditional Assignments + +For conditional assignments where both branches return single-line values of similar length, prefer the `if-else` format **without braces**: + +```kotlin +// ✅ Preferred +val dbUri = + if (type.equals(ODatabaseType.PLOCAL.name, ignoreCase = true)) + "$type:$name" + else "$type:" + +orient = OrientDB(dbUri, OrientDBConfig.defaultConfig()) +``` + +**Not:** +```kotlin +// ❌ Avoid +val dbUri = if (type.equals(ODatabaseType.PLOCAL.name, ignoreCase = true)) { + "$type:$name" +} else { + "$type:" +} +``` + +### Trivial If Statements + +For simple if statements with a single-line body, prefer the compact 2-line format without braces: + +```kotlin +// ✅ Preferred +if (!schema.existsClass(Schema.DOCUMENTS)) + createDocType(schema) + +if (!needed) + clearCache = updateTemplateSignatureIfChanged(templateDir) +``` + +**Not:** +```kotlin +// ❌ Avoid unnecessary braces for simple statements +if (!schema.existsClass(Schema.DOCUMENTS)) { + createDocType(schema) +} +``` + +**Exception:** Use braces when the if body contains multiple statements or complex logic. + +### Reducing Duplication + +When only a small part varies in similar code blocks, extract the varying part to eliminate duplication: + +```kotlin +// ✅ Preferred - extract the variable URL +val dbUri = + if (type.equals(ODatabaseType.PLOCAL.name, ignoreCase = true)) + "$type:$name" + else "$type:" + +orient = OrientDB(dbUri, OrientDBConfig.defaultConfig()) +``` + +**Not:** +```kotlin +// ❌ Duplicates the constructor call +orient = + if (type.equals(ODatabaseType.PLOCAL.name, ignoreCase = true)) + OrientDB("$type:$name", OrientDBConfig.defaultConfig()) + else + OrientDB("$type:", OrientDBConfig.defaultConfig()) +``` + +### SQL Conventions + +Always write SQL keywords in **UPPERCASE**: + +```kotlin +// ✅ Preferred +private const val STATEMENT_GET_ALL_CONTENT = "SELECT * FROM Documents WHERE type='%s' ORDER BY date DESC" + +fun getDocumentByUri(uri: String?): DocumentList { + return query("SELECT * FROM Documents WHERE sourceuri=?", uri) +} +``` + +**Not:** +```kotlin +// ❌ Lowercase SQL keywords +private const val STATEMENT_GET_ALL_CONTENT = "select * from Documents where type='%s' order by date desc" +``` + +### Long Lines for Constants + +For SQL statements and similar constants, prefer keeping them on a single line even if they're long: + +```kotlin +// ✅ Preferred +companion object { + private const val STATEMENT_GET_PUBLISHED_POST_BY_TYPE_AND_TAG = "SELECT * FROM Documents WHERE status='published' AND type='%s' AND ? IN tags ORDER BY date DESC" + private const val STATEMENT_GET_DOCUMENT_STATUS_BY_DOCTYPE_AND_URI = "SELECT sha1,rendered FROM Documents WHERE sourceuri=?" +} +``` + +**Not:** +```kotlin +// ❌ Unnecessary line breaks for constants +companion object { + private const val STATEMENT_GET_PUBLISHED_POST_BY_TYPE_AND_TAG = + "SELECT * FROM Documents WHERE status='published' " + + "AND type='%s' AND ? IN tags ORDER BY date DESC" +} +``` + +### Nullability + +During the Java-to-Kotlin migration, many nullable types were added automatically. Apply common sense and test semantics to determine what should actually be non-nullable: + +```kotlin +// ✅ Most setters shouldn't accept null +fun setPreviousContent(previousDocumentModel: DocumentModel) { + // Not DocumentModel? - why would you set a null value? +} + +// ✅ Query methods shouldn't accept null for required parameters +fun getPublishedContent(docType: String): DocumentList { + // Not String? - querying for null doesn't make sense +} +``` + +Use `lateinit` for properties that are initialized in `@BeforeEach`/`@Before` methods: +```kotlin +private lateinit var config: DefaultJBakeConfiguration +``` + +### Property Access vs Getters + +Prefer property access over getter methods when using Kotlin code: + +```kotlin +// ✅ Preferred +config.contentDir +config.destinationDir + +// ❌ Avoid (Java-style) +config.getContentDir() +config.getDestinationDir() +``` + +## Testing + +- All tests should pass before submitting a pull request +- Add tests for new functionality +- Update existing tests when changing behavior + +### Database Setup in Tests + +For tests that use OrientDB, ensure proper setup and cleanup: + +```kotlin +@BeforeEach +fun setUp() { + // Initialize database +} + +@AfterEach +fun tearDown() { + if (contentStore?.isActive == true) { + contentStore?.close() + contentStore?.shutdown() + } +} +``` + +## Commit Messages + +- Use clear, descriptive commit messages +- Start with a verb in present tense: "Add", "Fix", "Update", "Remove" +- Reference issue numbers when applicable + +## Pull Requests + +1. Fork the repository +2. Create a feature branch from `master` +3. Make your changes following these guidelines +4. Ensure all tests pass +5. Submit a pull request with a clear description of changes + +## Questions? + +If you have questions about these guidelines or need clarification, please open an issue for discussion. + +--- + +Thank you for contributing to JBake! + +# Next steps + +- Migrate the templating engines to their latest versions +- Continue refactoring the central map into type-safe models +- Reduce the warnings from the Kotlin compiler +- Rebrand to KBake? +- Set up publising as per https://github.com/OndraZizka/csv-cruncher +- Publish a new release 4.0.0 under ch.zizka.kbake groupId diff --git a/docs/ModelAttributes-analysis.md b/docs/ModelAttributes-analysis.md new file mode 100644 index 000000000..ecb000ac5 --- /dev/null +++ b/docs/ModelAttributes-analysis.md @@ -0,0 +1,38 @@ +# Model Attributes Data Flow Matrix + +| Key | Type | Assigned By | Accessed By | Notes | +|----------------------------------|-------------------------------| --- | --- | --- | +| `DOC_BODY_RENDERED` | `String` | ParserContext / MarkupEngine | DocumentModel, HsqldbContentRepository, Neo4jContentRepository | Rendered HTML body captured from markup parsing | +| `DOC_DATE` | `Date?` | AsciidoctorEngine, ParserContext | DocumentModel, Crawler (status transition logic), tests | Null means no explicit date provided | +| `DOC_NAME` | `String` | BaseModel / TypedBaseModel (DB hydration) | DocumentModel | Derived from filename or DB row | +| `DOC_STATUS` | `String?` | AsciidoctorEngine, Crawler.addAdditionalDocumentAttributes | DocumentModel, Renderer, Crawler, OrientDBContentRepository | Defaults to empty string; converted from published-date to published | +| `DOC_TAGS` | `List` | AsciidoctorEngine, Parser.setTags | DocumentModel, Renderer | Empty when no tags declared | +| `DOC_TITLE` | `String?` | AsciidoctorEngine / ParserContext | DocumentModel | Null when no title header present | +| `DOC_TYPE` | `String` | AsciidoctorEngine, Crawler (data files), Renderer.buildSimpleModel | DocumentModel, OrientDBContentRepository | Describes logical doc type (post/page/index/etc.) | +| `FS_DOC_SHA1` | `String?` | Crawler.addAdditionalDocumentAttributes | DocumentModel, OrientDBContentRepository | Change detector hash | +| `FS_DOC_SOURCE_PATH_ABS` | `String?` | Crawler.addAdditionalDocumentAttributes | DocumentModel | Absolute file path | +| `FS_DOC_OUTPUT_URI_NOEXT` | `String?` | Crawler.addAdditionalDocumentAttributes | DocumentModel | URI variant without extension | +| `FS_DOC_IS_CACHED_IN_DB` | `Boolean` | Crawler.addAdditionalDocumentAttributes | DocumentModel, OrientDBContentRepository | Marks cached rows | +| `FS_DOC_WAS_RENDERED` | `Boolean` | Crawler.addAdditionalDocumentAttributes, DB hydration | DocumentModel, OrientDBContentRepository | Tracks rendering status | +| `FS_DOC_OUTPUT_URI` | `String?` | Crawler.addAdditionalDocumentAttributes, DefaultRenderingConfig.model | DocumentModel | Final output URI | +| `FS_DOC_SOURCE_REL_URI` | `String?` | Crawler.addAdditionalDocumentAttributes, DB hydration | DocumentModel, OrientDBContentRepository | Source path relative to content dir | +| `FS_REL_FROM_DOC_TO_SITEROOT` | `String` | Crawler.addAdditionalDocumentAttributes, Renderer.buildSimpleModel | DocumentModel, tests | Root path helper | +| `TMPL_CONTENT_MODEL` | `DocumentModel` | Renderer.render / renderIndexPaging / renderTags, DefaultRenderingConfig.model, TemplateModel.fromContext | TemplateModel | Primary per-render content | +| `TMPL_DB_ACCESS` | `ContentStore` | DelegatingTemplateEngine eager model, FreemarkerTemplateEngine wrappers | FreemarkerTemplateEngine adapters | Enables db access from templates | +| `TMPL_OUT_WRITER` | `Writer` | DelegatingTemplateEngine.renderDocument | TemplateModel | Underlying output writer | +| `TMPL_JBAKE_CONFIG` | `Map` | Renderer.renderTags, Renderer.tags index builder, TemplateModel.fromContext, FreemarkerTemplateEngine merged config | TemplateModel, FreemarkerTemplateEngine | Merged underscore + dotted config | +| `TMPL_ENGINE` | `DelegatingTemplateEngine?` | Renderer render flows, DefaultRenderingConfig.model | TemplateModel | Gives templates access to render delegates | +| `PAGI_CUR_PAGE_NUMBER` | `Int` | Renderer.renderIndexPaging, DefaultRenderingConfig.model, TemplateModel.fromContext | TemplateModel | Current pagination index | +| `PAGI_NEXT_CONTENT` | `DocumentModel?` | DocumentsRenderingTool.getContentForNav | DocumentModel, Renderer | Next doc link | +| `PAGI_NEXT_FILENAME` | `String?` | Renderer.renderIndexPaging, DefaultRenderingConfig.model, TemplateModel.fromContext | TemplateModel | Next page file | +| `PAGI_PREV_CONTENT` | `DocumentModel?` | DocumentsRenderingTool.getContentForNav | DocumentModel, Renderer | Prev doc link | +| `PAGI_PREV_FILENAME` | `String?` | Renderer.renderIndexPaging, DefaultRenderingConfig.model, TemplateModel.fromContext | TemplateModel | Prev page file | +| `PAGI_TOTAL_PAGES_COUNT` | `Int` | Renderer.renderIndexPaging, DefaultRenderingConfig.model, TemplateModel.fromContext | TemplateModel | Total pagination pages | +| `TAGS_ALL` | `List` | Renderer.renderTagsIndex, TagsExtractor.collectTags | FreemarkerTemplateEngine, tag templates | Distinct tag list | +| `TAGS_CURRENT_TAG` | `String?` | Renderer.renderTags, TemplateModel.fromContext | TemplateModel, Renderer | Currently processed tag | +| `TAGS_DOCS_TAGGED_CUR` | `DocumentList` | TagsExtractor.populateTagContext, TemplateModel.fromContext | TemplateModel | Documents filtered by tag | +| `TAGS_POSTS_TAGGED_CUR` | `DocumentList` | TagsExtractor.populateTagContext, TemplateModel.fromContext | TemplateModel | Posts filtered by tag | +| `DATA_FILES` | `Map` | FreemarkerTemplateEngine (DataFileUtil adapter), TemplateModel.fromContext | FreemarkerTemplateEngine | Exposes data dir records | +| `GLOB_PUBLISHING_DATE_FORMATTED` | `String` | FreemarkerTemplateEngine | FreemarkerTemplateEngine/templates | Formatted publish date | +| `GLOB_JBAKE_VERSION` | `String` | Renderer.render, DefaultRenderingConfig.model, TemplateModel.fromContext | TemplateModel | JBake version string | + diff --git a/docs/TEXY.md b/docs/TEXY.md new file mode 100644 index 000000000..54670f215 --- /dev/null +++ b/docs/TEXY.md @@ -0,0 +1,255 @@ +# Texy Markup Engine Support + +JBake now supports [Texy](https://texy.info/), a markup language from the Czech Republic that converts plain text to XHTML/HTML5. + +## Overview + +The Texy engine allows JBake to process `.texy` files by communicating with a Texy service via HTTP. This approach keeps JBake lightweight while allowing support for various markup formats through external services. + +## Requirements + +The Texy engine requires a running Texy service that accepts HTTP POST requests. You have several options: + +1. **Docker** (Recommended): + ```bash + docker run -d -p 8080:8080 --name texy-service + ``` + +2. **Standalone Service**: + You can run a custom Texy service implementation that exposes an HTTP endpoint. + +3. **Custom Implementation**: + Implement your own Texy service that accepts POST requests with Texy markup and returns HTML. + +## Configuration + +Add the following configuration to your `jbake.properties` file: + +```properties +# Texy service URL (default: http://localhost:8080/texy) +texy.service.url=http://localhost:8080/texy + +# Connection timeout in milliseconds (default: 5000) +texy.connection.timeout=5000 + +# Read timeout in milliseconds (default: 10000) +texy.read.timeout=10000 +``` + +## Usage + +### Creating a Texy Document + +Create a file with the `.texy` extension in your content directory: + +**example.texy**: +``` +title=My Texy Document +status=published +type=post +date=2025-12-01 +tags=texy,markup +~~~~~~ + +Heading +======= + +This is a **bold** text and this is an //italic// text. + +- First item +- Second item +- Third item + +Links +----- + +"Link text":http://example.com + +Images +------ + +[* image.jpg *] + +Tables +------ + +|---- +| Name | Age +|---- +| John | 25 +| Jane | 30 +|---- +``` + +### Texy Markup Syntax + +Texy supports various markup features: + +- **Bold**: `**text**` or `*text*` +- **Italic**: `//text//` or `/text/` +- **Links**: `"link text":http://url` or `[link text|http://url]` +- **Images**: `[* image.jpg *]` or `[* image.jpg >*]` (with alignment) +- **Lists**: Lines starting with `-` or `*` (unordered) or numbers (ordered) +- **Tables**: Using `|----` delimiters +- **Headings**: + - `Heading\n=======` (level 1) + - `Heading\n-------` (level 2) + - Or `#Heading` style + +For complete Texy syntax, visit [Texy Documentation](https://texy.info/en/). + +## Service API + +The Texy service must implement the following API: + +**Endpoint**: `POST /texy` (or your configured URL) + +**Request**: +- Content-Type: `text/plain; charset=UTF-8` +- Body: Raw Texy markup text + +**Response**: +- Content-Type: `text/html; charset=UTF-8` +- Body: Rendered HTML +- Status: `200 OK` on success + +**Example Service Implementation** (pseudocode): +``` +POST /texy +Accept: text/html +Content-Type: text/plain + +Input: Texy markup text +Output: Rendered HTML +``` + +## Docker Service Module + +A complete Maven module for the Texy service is provided in `jbake-texy-service/`: +- Full Maven build integration +- Automated Docker image building +- End-to-end tests with TestContainers +- JBake integration tests +- Docker Hub push capabilities + +### Quick Start with Maven + +```bash +# Build the Docker image +cd jbake-texy-service/ +mvn package + +# Run the service +docker run -d -p 8080:8080 --name texy jbake/texy-service:latest + +# Test the service +curl -X POST http://localhost:8080/texy \ + -H "Content-Type: text/plain" \ + -d "This is **bold** and this is //italic//" + +# Run tests (includes Docker image build and container tests) +mvn test + +# Stop the service +docker stop texy && docker rm texy +``` + +### Quick Start with Gradle + +```bash +# Build the Docker image +cd jbake-texy-service/ +./gradlew assemble + +# Run the service +docker run -d -p 8080:8080 --name texy jbake/texy-service:latest + +# Run tests (includes Docker image build and container tests) +./gradlew test + +# Stop the service +docker stop texy && docker rm texy +``` + +### Building and Running with Docker Directly + +```bash +# Build from source +cd jbake-texy-service/ +docker build -t texy-service . + +# Or pull from Docker Hub (once published) +docker pull jbake/texy-service:latest + +# Run the service +docker run -d -p 8080:8080 --name texy jbake/texy-service:latest +``` + +The service will be available at `http://localhost:8080/texy` + +## Architecture + +The implementation follows JBake's existing pattern for markup engines: + +1. **Engine Registration**: The `TexyEngine` class is registered in `MarkupEngines.properties` +2. **File Processing**: JBake routes `.texy` files to `TexyEngine` +3. **Header Parsing**: Standard JBake header parsing (title, status, type, date, etc.) +4. **Body Rendering**: HTTP POST request to Texy service with markup content +5. **Error Handling**: Graceful degradation if service is unavailable + +## Key Features + +- ✅ HTTP-based service communication (Docker or standalone) +- ✅ Configurable timeouts and service URL +- ✅ Full Texy markup support via external service +- ✅ Standard JBake header metadata parsing +- ✅ Graceful error handling +- ✅ Written in Kotlin +- ✅ Follows existing JBake patterns +- ✅ Comprehensive tests +- ✅ Complete documentation + +## Troubleshooting + +### Service Not Available + +If the Texy service is not available, the engine will wrap the error message and original content in a `
` tag. Check:
+
+1. Is the Texy service running?
+   ```bash
+   curl -X POST http://localhost:8080/texy -d "test" -H "Content-Type: text/plain"
+   ```
+
+
+### Timeout Issues
+
+If you're processing large documents, increase the timeout values:
+
+```properties
+texy.connection.timeout=10000
+texy.read.timeout=30000
+```
+
+### Character Encoding
+
+Ensure your Texy files are saved in UTF-8 encoding to avoid character issues.
+
+## Performance Considerations
+
+Since the Texy engine makes HTTP requests for each file, consider:
+
+1. **Service Performance**: Ensure your Texy service is optimized and can handle multiple requests.
+2. **Network Latency**: Run the Texy service locally or on the same network as JBake.
+3. **Caching**: JBake's built-in caching will help avoid re-processing unchanged files.
+4. **Batch Processing**: For large sites, consider processing in batches or optimizing your service.
+
+## Credits
+
+- **Texy**: Created by David Grudl (https://texy.info/)
+
+## Links
+
+- Texy Official Site: https://texy.info/
+- Texy Documentation: https://texy.info/en/
+- Texy GitHub: https://github.com/dg/texy
+- JBake Website: https://jbake.org/
diff --git a/docs/ai-rules.md b/docs/ai-rules.md
new file mode 100644
index 000000000..ca1faec60
--- /dev/null
+++ b/docs/ai-rules.md
@@ -0,0 +1,92 @@
+
+This document provides guidelines for contributing to **JBake**, an open-source static site generator written in Kotlin.
+
+**Project Context:**
+- Primary language: Kotlin (with Java interoperability)
+- Build systems: Maven and Gradle (both must be maintained)
+- Key technologies: Asciidoc, Freemarker, Thymeleaf, Markdown parsers
+- Users: Site generators who rely on stable APIs
+
+**Core Principles:**
+- Maintain backwards compatibility for existing JBake users
+- Write concise, idiomatic Kotlin code
+- Minimize unnecessary code - question whether new code is needed
+- Run unit tests frequently; run full test suite (including IT and E2E) before finalizing changes
+- Keep dependency versions current while ensuring compatibility
+
+## Environment Setup
+
+- JBake test suite requires JDK 17. Use Eclipse Temurin JDK 17 to run the tests.
+  ~~`export JAVA_HOME=$HOME/.jdks/temurin-17.0.17 && export PATH=$JAVA_HOME/bin:$PATH && mvn ...`~~
+    - Update: Not needed anymore, Maven is now set up with `` so it works with just `mvn ...`.
+
+## Build System
+
+- When modifying build files, ensure compatibility with both Maven and Gradle builds. Update both `pom.xml` and `build.gradle` as needed.
+- When adding new dependencies, prefer using BOMs to manage versions consistently across Maven and Gradle.
+- Maven: Put the  elements on a single line each.
+- Maven and Gradle: When adding plugins, ensure they are added to both `pom.xml` and `build.gradle` files.
+
+## Code Style & Idioms
+
+- Check the docs/CONTRIBUTING.md file for coding style.
+- Prefer Kotlin idioms over Java ones where possible.
+    * runCatching instead of try-catch when both `try` and `catch` blocks are short.
+    * Use Kotlin's rich collection APIs.
+    * Kotlin 2.2.x has a syntax of $$"..." where you do not need to escape dollar symbols using ${'$'}. So keep those where they exist, and use them where sensible.
+    * DO NOT REPLACE THE $$"..." syntax with escaped dollars!!
+- Prefer brief code constructs over longer ones. For instance:
+  - Use `mapNotNull` instead of `map` followed by `filterNotNull`.
+  - If an `if` contains only `return`, `break`, or `continue`, then put it on a single line. I.e.:
+    - `if (inBlockComment) continue`
+    - `if (isMeaningfulLine(line))\n    count++`
+  - Exit the function early instead of wrapping the main logic in an `if` block.
+  - Use lambda logging using Slf4j's lazy logging: `logger.debug { "Expensive log message: ${compute()}" }`.
+  - Put the logger `log` instance as a private member of the class (at the end of it) instead of using companion object.
+  - Use single-expression functions when they are short.
+      - For longer ones, put the `=` indented on the next line.
+      - Prefer returning simpler values in `if` rather than later (in `else` or after the `if`).
+        - ```kotlin
+          //  Not so great:
+          if (!templateFileName.isNullOrEmpty()) {
+              return templateDir.resolve(templateFileName)
+          }
+          return null
+
+          // Better:
+          if (templateFileName.isNullOrEmpty()) return null
+          return templateDir.resolve(templateFileName)
+          ```
+- Don't leave empty lines before closing braces.
+- Try not to duplicate code. If you find yourself copying and pasting code, consider refactoring it into a shared function, class, or module.
+- Keep Java interop in mind. JBake is also used as a library. New public APIs should be easily usable from Java.
+- When you add code intended for debugging or investigation, mark it somehow, e.g. with `... // DEBUG` comment or `// +++ DEBUG block start` and `// +++ DEBUG block end`, so it can be easily found and removed later.
+- Add dots at the end of comment sentences, and capitalize them.
+
+## Testing
+
+- Write unit tests for new features and bug fixes. Use Kotest and Mockk for testing. Kotest allows parametrized tests with `forAll` and `row`. Use StringSpec rather than FunSpec. Do NOT use JUnit, especially not constructs like `assertTrue(foo.contains("bar"))`!
+- When you assume a task is done, run the full test suite with both Maven and Gradle. `mvn clean verify` and `./gradlew clean test`. Include E2E tests.
+  - The run may take around a minute.
+
+## Development Workflow
+
+- Feel free to suggest large-scale refactoring if you see opportunities to improve code structure or readability.
+- Avoid running exec commands in CLI, as the developer has to confirm them.
+  - For instance, `find ... -exec` or `sed -i` are discouraged. Use IDEA's Find and Replace tool instead.
+  - Instead of `find ... | grep ...` or `find ... -exec ...`, prefer using `rg`. If not installed, prompt the user to install `ripgrep`.
+- When running CLI, add the motivation for the command as a very brief comment after it, e.g. `rg 'somePattern'  # Expecting it to appear in ...`.
+- Write scripts as Kotlin Scripts (.kts) when practical. Avoid Python. Avoid Bash except for short scripts where Kotlin alternative would be more than twice as long.
+- When invoking the command line, remember to add batch mode options to avoid interactive prompts during automated processes, e.g., `--batch-mode` for Maven commands, `--no-pager` for Git commands, etc.
+- To get the usages of ModelAttributes, run this: `rg 'import .+ModelAttributes' --type kotlin | grep '.kt' | cut -f1 -d':' | xargs grep -B6 -A4 'ModelAttributes' {} | grep -v import`
+
+## Agent Behavior
+
+- Do not generate lengthy explanations or justifications for your actions. Focus on providing concise, actionable code changes or suggestions. Assume the user knows Kotlin, Java, Docker, and the frameworks used. Do not explain basic concepts. Do not apologize. Do not exaggerate by saying "you are absolutely right" or similar; challenge the prompts with your expertise after analyzing them the user's prompt.
+- Specifically for Grok agent: You tend not to provide any comments on your actions. Provide concise reasoning for your code changes when non-trivial. Comment on results of test runs and findings from web.
+- Do not generate lengthy reports. The user is supposed to read and understand the code. If some construct is complex, add concise comments in the code itself. But ideally, rely on perfect naming and structure.
+- Specifically for Gemini agent: After stating "I will do X now", do it, rather than stopping.
+- If you detect "corrupted" file, it is probably the developer doing changes. Pause for 10 seconds and retry reading it. If still corrupted, mention it concisely and ask the user to fix it.
+- IDE console tool has some weird bug that the first command in a given console gets split to two lines after the first minus and fails. If that happens, run the same command again, that works.
+- Do not remove in-line comments unless they become false by the changes of the code.
+- Do not remove commented-out parts of the code without being asked for it. The user will actively ask for a cleanup.
diff --git a/docs/jbake-default-author-example.properties b/docs/jbake-default-author-example.properties
new file mode 100644
index 000000000..aa2d7bc1d
--- /dev/null
+++ b/docs/jbake-default-author-example.properties
@@ -0,0 +1,61 @@
+# JBake Configuration Example - Default Author Feature
+
+# Basic site configuration
+site.host=https://example.com
+render.tags=true
+render.archive=true
+
+# Default values for content
+default.status=published
+default.type=post
+
+# NEW: Default author - will be used when documents don't specify an author
+# This is particularly useful for:
+# - Personal blogs where all posts have the same author
+# - Migration from systems that didn't track authors
+# - Template compatibility when ${content.author} is referenced
+default.author=Your Name Here
+
+# You can leave it empty to not use a default author
+# default.author=
+
+# Example usage in documents:
+
+# 1. Old JBake Header Format (.html, .md, etc with ~~~~~~ separator)
+#    title=My Blog Post
+#    date=2023-12-04
+#    type=post
+#    status=published
+#    ~~~~~~
+#    Content...
+#    ? Will use "Your Name Here" as author
+
+# 2. With explicit author (overrides default)
+#    title=Guest Post
+#    author=Guest Writer
+#    date=2023-12-04
+#    type=post
+#    status=published
+#    ~~~~~~
+#    Content...
+#    ? Will use "Guest Writer" as author
+
+# 3. Asciidoc Native Format (.adoc, .ad, .asciidoc)
+#    = Blog Post Title
+#    :jbake-type: post
+#    :jbake-status: published
+#    :jbake-date: 2023-12-04
+#
+#    Content...
+#    ? Will use "Your Name Here" as author
+
+# 4. Asciidoc with author line (overrides default)
+#    = Blog Post Title
+#    Guest Writer
+#    2023-12-04
+#    :jbake-type: post
+#    :jbake-status: published
+#
+#    Content...
+#    ? Will use "Guest Writer" as author
+
diff --git a/docs/maven-migration/MAVEN_QUICK_REFERENCE.md b/docs/maven-migration/MAVEN_QUICK_REFERENCE.md
new file mode 100644
index 000000000..a45946d2f
--- /dev/null
+++ b/docs/maven-migration/MAVEN_QUICK_REFERENCE.md
@@ -0,0 +1,325 @@
+# JBake Maven - Quick Reference Cheat Sheet
+
+## Essential Commands
+
+```bash
+# Build
+mvn clean compile          # Compile only
+mvn clean package          # Build JAR/distribution
+mvn clean verify           # Full build with tests
+mvn clean install          # Build and install to local repo
+
+# Build specific module
+mvn -pl :jbake-core compile
+mvn -pl :jbake-dist package
+mvn -pl :jbake-maven-plugin verify
+
+# Skip tests
+mvn package -DskipTests
+mvn clean verify -DskipTests
+
+# Parallel build (threads)
+mvn -T 1C clean verify     # 1 thread per core
+```
+
+## Testing
+
+```bash
+# Unit tests only
+mvn test
+
+# All tests (unit + integration)
+mvn verify
+
+# Specific test
+mvn test -Dtest=MainTest
+
+# Skip tests during package
+mvn package -DskipTests
+
+# Smoke tests only (jbake-dist)
+mvn -pl :jbake-dist verify
+
+# With coverage report
+mvn verify jacoco:report
+# Open: target/site/jacoco/index.html
+```
+
+## Code Quality
+
+```bash
+# Generate coverage report
+mvn jacoco:report
+
+# Check code style
+mvn checkstyle:check
+
+# Generate checkstyle report
+mvn checkstyle:checkstyle
+# Open: target/site/checkstyle.html
+
+# Full quality check
+mvn verify jacoco:report checkstyle:check
+```
+
+## Modules
+
+```bash
+# List modules
+mvn -pl help:active-profiles
+
+# Build all
+mvn clean verify
+
+# Build specific module
+mvn -pl :jbake-core clean verify
+
+# Build module and dependencies
+mvn -pl jbake-core -am clean verify
+
+# Build module and dependents
+mvn -pl jbake-core -amd clean verify
+```
+
+## Cleaning
+
+```bash
+# Clean build directory
+mvn clean
+
+# Clean and build
+mvn clean compile
+
+# Remove local jbake artifacts
+rm -rf ~/.m2/repository/org/jbake/
+
+# Clean all (with cache clear)
+rm -rf ~/.m2/repository/org/jbake && mvn clean
+```
+
+## Dependencies
+
+```bash
+# Show dependency tree
+mvn dependency:tree
+
+# Show for specific module
+mvn -pl :jbake-core dependency:tree
+
+# Check for conflicts
+mvn dependency:tree -Dverbose
+
+# Resolve plugins
+mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin
+
+# Update dependencies
+mvn versions:display-dependency-updates
+```
+
+## Packaging & Distribution
+
+```bash
+# Build core library JAR
+mvn -pl :jbake-core package
+
+# Build distribution
+mvn -pl :jbake-dist package
+# Outputs: ZIP, TAR, JAR
+
+# Build Maven plugin
+mvn -pl :jbake-maven-plugin package
+
+# Build everything
+mvn package
+```
+
+## Release & Deploy
+
+```bash
+# Snapshot to local repository
+mvn install
+
+# Deploy to Sonatype OSSRH (requires credentials & GPG)
+mvn clean deploy -P release
+
+# Dry run without signing
+mvn clean deploy -P release -Darguments="-DskipSigning"
+
+# Stage for release (Maven Release Plugin)
+mvn release:prepare
+mvn release:perform
+```
+
+## Plugin Goals
+
+```bash
+# Compiler
+mvn compiler:compile         # Compile
+mvn compiler:testCompile     # Test compile
+
+# Surefire (unit tests)
+mvn surefire:test
+mvn surefire:test -Dtest=MainTest
+
+# Failsafe (integration tests)
+mvn failsafe:integration-test
+mvn failsafe:verify
+
+# JAR
+mvn jar:jar                  # Create JAR
+
+# Assembly (distribution)
+mvn assembly:single         # Create distribution
+
+# JaCoCo
+mvn jacoco:report           # Generate coverage
+mvn jacoco:prepare-agent    # For custom test runs
+
+# Help
+mvn help:describe -Dplugin=org.jbake:jbake-maven-plugin
+```
+
+## Common Issues & Fixes
+
+```bash
+# OutOfMemoryError during build
+export MAVEN_OPTS="-Xmx3g"
+
+# Can't find symbol (Kotlin issue)
+mvn clean compile  # Ensures Kotlin compiles first
+
+# Plugin not found
+mvn help:describe -Dplugin=:jbake-maven-plugin
+
+# Slow builds
+mvn -T 1C clean verify  # Parallel threads
+
+# Dependency version conflicts
+mvn dependency:tree -Dverbose
+
+# Clear everything and rebuild
+rm -rf ~/.m2/repository/org/jbake && mvn clean verify
+```
+
+## Environment Variables
+
+```bash
+# More memory for builds
+export MAVEN_OPTS="-Xmx3g -XX:MaxPermSize=512m"
+
+# Debug mode
+export MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"
+
+# Verbose output
+mvn -X clean verify
+
+# Quiet mode
+mvn -q clean verify
+```
+
+## Project Structure
+
+```
+jbake/
+├── pom.xml                    ← Start here (parent)
+├── jbake-core/pom.xml
+├── jbake-dist/pom.xml
+├── jbake-maven-plugin/pom.xml
+├── MAVEN_MIGRATION_COMPLETE.md  ← Full guide
+├── KOTLIN_COMPILATION.md         ← Kotlin setup
+└── Documentation files
+```
+
+## POM Reference
+
+### Properties
+```xml
+
+    1.8
+    2.2.0
+    5.8.2
+
+```
+
+### BOM Import
+```xml
+
+    
+        
+            org.jetbrains.kotlin
+            kotlin-bom
+            ${kotlin.version}
+            pom
+            import
+        
+    
+
+```
+
+### Dependency
+```xml
+
+    commons-io
+    commons-io
+    
+
+```
+
+## Version Info
+
+- **Maven**: 3.9.x+
+- **Java**: 1.8+
+- **Kotlin**: 2.2.0
+- **JUnit**: 5.8.2
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `pom.xml` | Parent POM, dependency mgmt |
+| `jbake-core/pom.xml` | Core library |
+| `jbake-dist/pom.xml` | CLI distribution |
+| `jbake-maven-plugin/pom.xml` | Maven plugin |
+| `config/checkstyle/checkstyle.xml` | Code style rules |
+| `jbake-dist/src/assembly/distribution.xml` | Distribution config |
+
+## Help & Resources
+
+```bash
+# Built-in help
+mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin
+
+# List available goals for plugin
+mvn help:describe -Dplugin=:maven-compiler-plugin -Ddetail
+
+# JBake plugin help
+mvn help:describe -Dplugin=org.jbake:jbake-maven-plugin
+
+# Full POM
+mvn help:effective-pom
+
+# Effective settings
+mvn help:effective-settings
+```
+
+## Tips & Tricks
+
+```bash
+# Skip javadoc generation (faster)
+mvn package -Dskip.javadoc=true
+
+# Skip all quality checks
+mvn package -DskipTests -Dskip.jacoco=true -Dskip.checkstyle=true
+
+# Resume build from failure point
+mvn -rf :jbake-dist clean verify
+
+# Show download progress
+mvn -B clean verify
+
+# Build in offline mode (no internet)
+mvn -o clean compile
+
+# Show effective POM
+mvn help:effective-pom > effective-pom.xml
+```
diff --git a/docs/releasing/RELEASING.md b/docs/releasing/RELEASING.md
new file mode 100644
index 000000000..6ab7417b2
--- /dev/null
+++ b/docs/releasing/RELEASING.md
@@ -0,0 +1,232 @@
+# Releasing JBake to Maven Central
+
+This document describes how to release JBake artifacts to Maven Central under the `ch.zizka.jbake` groupId.
+
+It's under this groupId for this branch. If/when merged, it may be changed to `org.jbake`.
+
+
+## Quick Start
+
+```bash
+export JAVA_HOME=$HOME/.jdks/temurin-17.0.17
+export PATH=$JAVA_HOME/bin:$PATH
+export GPG_TTY=$(tty)
+mvn clean deploy -Prelease
+```
+
+Or use the deployment script:
+```bash
+./deploy-to-central.sh
+```
+
+## Prerequisites
+
+### 1. Java 17
+
+JBake requires Java 17 for building:
+
+```bash
+export JAVA_HOME=$HOME/.jdks/temurin-17.0.17
+export PATH=$JAVA_HOME/bin:$PATH
+java -version  # Verify it's Java 17
+```
+
+### 2. Maven Central Portal Account
+
+- Portal: https://central.sonatype.com/
+- Login with your OSSRH JIRA credentials
+- Ensure you may publish to `ch.zizka`
+
+### 3. Maven Settings (~/.m2/settings.xml)
+
+```xml
+
+  
+    
+      central
+      YOUR_TOKEN_USERNAME
+      YOUR_TOKEN_PASSWORD
+    
+  
+
+  
+    
+      ossrh
+      
+        true
+      
+      
+        gpg
+          
+      
+    
+  
+
+```
+
+To get fresh credentials:
+1. Go to https://central.sonatype.com/account
+2. Click "Generate User Token"
+3. Copy the credentials to settings.xml
+
+### 4. GPG Key
+
+Your GPG key must be uploaded to public keyservers.
+
+#### If you already have a key at $HOME/.ssh/OSSRH_gpg.key:
+
+```bash
+./import-gpg-key.sh
+```
+
+#### To generate a new key:
+
+```bash
+gpg --full-generate-key
+# Key type: RSA and RSA (default)
+# Key size: 4096
+# Expiration: 0 (never expires)
+# Name: Ondrej Zizka
+# Email: ...
+# Passphrase: (optional, can be empty)
+```
+
+#### Upload public key to keyservers:
+
+```bash
+# Get your key ID
+gpg --list-secret-keys --keyid-format=long
+
+# Upload (replace KEY_ID with actual ID)
+gpg --keyserver keys.openpgp.org --send-keys KEY_ID
+gpg --keyserver keyserver.ubuntu.com --send-keys KEY_ID
+```
+
+## Deployment Methods
+
+### Method 1: Direct Deployment (Recommended)
+
+```bash
+export JAVA_HOME=$HOME/.jdks/temurin-17.0.17
+export PATH=$JAVA_HOME/bin:$PATH
+export GPG_TTY=$(tty)
+mvn clean deploy -Prelease
+```
+
+This will:
+1. Build all modules
+2. Generate sources and javadoc JARs (using Dokka for Kotlin code)
+3. Sign all artifacts with GPG
+4. Upload to Maven Central Portal
+5. Automatically validate and publish (autoPublish=true)
+
+### Method 2: Using Deployment Script
+
+```bash
+./deploy-to-central.sh
+```
+
+The script performs the same steps with interactive prompts and validation.
+
+### Method 3: Using maven-release-plugin
+
+For version management and Git tagging:
+
+```bash
+export JAVA_HOME=$HOME/.jdks/temurin-17.0.17
+export PATH=$JAVA_HOME/bin:$PATH
+export GPG_TTY=$(tty)
+mvn release:prepare -Prelease  ## Updates versions, creates Git tag.
+mvn release:perform -Prelease  ## Builds and uploads.
+```
+
+## After Deployment
+
+### Timeline
+
+- **Upload**: Immediate
+- **Validation**: 1-5 minutes
+- **Maven Central**: 15-30 minutes
+- **Search Index**: 2-24 hours
+
+### Verification
+
+1. **Central Portal** (immediate):
+   https://central.sonatype.com/publishing
+
+2. **Maven Central Repository** (15-30 min):
+   https://repo1.maven.org/maven2/ch/zizka/jbake/
+
+3. **Maven Search** (few hours):
+   https://search.maven.org/search?q=g:ch.zizka.jbake
+
+## Modules Published
+
+- `ch.zizka.jbake:jbake-base` - Parent POM
+- `ch.zizka.jbake:jbake-core` - Core library (with Kotlin KDoc documentation)
+- `ch.zizka.jbake:jbake-dist` - Distribution JAR
+- `ch.zizka.jbake:jbake-maven-plugin` - Maven plugin
+
+Note: `jbake-e2e-tests` is not deployed (has `maven.deploy.skip=true`)
+
+## Project Configuration
+
+### GPG Signing
+
+All artifacts are signed with GPG in the release profile:
+
+- Configured with `--pinentry-mode loopback`
+- Requires GPG_TTY environment variable
+- Public keys must be on keyservers
+
+## Troubleshooting
+
+### Error: GPG signing failed: No secret key
+
+Your GPG key is not imported.
+
+**Solution**:
+```bash
+./import-gpg-key.sh      ## Import existing key
+gpg --full-generate-key  ## Or generate new key
+```
+
+### Error: GPG signing failed: Inappropriate ioctl for device
+
+GPG_TTY is not set. Use `export GPG_TTY=$(tty)`.
+
+### Error: Invalid signature - Could not find public key
+
+Your GPG public key is not on keyservers.
+
+**Solution**:
+```bash
+# Get key ID
+gpg --list-secret-keys --keyid-format=long
+
+# Upload to keyservers
+gpg --keyserver keys.openpgp.org --send-keys YOUR_KEY_ID
+gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID
+```
+
+
+## Manual Review Before Publishing
+
+If you want to review artifacts before they're released to Maven Central:
+
+1. Change in `pom.xml`: `false`
+2. Deploy: `mvn clean deploy -Prelease`
+3. Review at: https://central.sonatype.com/publishing
+4. Click "Publish" to release to Maven Central
+
+## Helper Scripts
+
+- `deploy-to-central.sh` - Interactive deployment script
+- `import-gpg-key.sh` - Import existing GPG key
+- `setup-gpg-key.sh` - Generate new GPG key
+
+## Reference
+
+- Maven Central Portal: https://central.sonatype.com/
+- Portal Documentation: https://central.sonatype.org/publish/publish-portal-maven/
+- GPG Documentation: https://www.gnupg.org/documentation/S
diff --git a/gradle.properties b/gradle.properties
index b1554c042..34817c52c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,61 +1,80 @@
 group                       = org.jbake
-version                     = 2.7.0-SNAPSHOT
+version                     = 5.0.0-rc1
 description                 = JBake is a Java based open source static site/blog generator for developers.
 
 website                     = http://jbake.org
 issues                      = https://github.com/jbake-org/jbake/issues
 vcs                         = https://github.com/jbake-org/jbake/
 
-# runtime dependencies
-asciidoctorjVersion         = 2.5.7
-asciidoctorjDiagramVersion  = 2.2.1
-args4jVersion               = 2.33
-commonsIoVersion            = 2.11.0
-commonsConfigurationVersion = 2.7
-commonsBeanutilsVersion     = 1.9.4
-commonsLangVersion          = 3.12.0
-commonsVfs2Version          = 2.9.0
-freemarkerVersion           = 2.3.31
-flexmarkVersion             = 0.62.2
-groovyVersion               = 3.0.9
-jettyServerVersion          = 9.4.44.v20210927
+javaVersion                 = 17
+kotlinVersion               = 2.2.21
+
+
+# Runtime dependencies
+asciidoctorjVersion         = 3.0.1
+asciidoctorjDiagramVersion  = 3.1.0
+args4jVersion               = 2.37
+commonsIoVersion            = 2.21.0
+commonsConfigurationVersion = 2.13.0
+commonsBeanutilsVersion     = 1.11.0
+commonsLangVersion          = 3.20.0
+commonsVfs2Version          = 2.10.0
+freemarkerVersion           = 2.3.34
+flexmarkVersion             = 0.64.8
+groovyVersion               = 5.0.3
+#jettyServerVersion          = 12.1.4  Requires a migration.
+jettyServerVersion          = 11.0.26
 jsonSimpleVersion           = 1.1.1
+# Migrated to pug4j 2.x
 jade4jVersion               = 1.3.2
-jsoupVersion                = 1.14.3
-jgitVersion                 = 6.0.0.202111291000-r
-logbackVersion              = 1.2.10
-orientDbVersion             = 3.1.20
-pebbleVersion               = 3.1.5
-slf4jVersion                = 1.7.32
-snakeYamlVersion            = 1.30
-thymeleafVersion            = 3.0.14.RELEASE
-picocli                     = 4.6.2
-
-
-# testing dependencies
-junit4Version               = 4.13.2
-junit5Version               = 5.8.2
-junit5SystemExtVersion      = 1.2.0
-junitPioneer                = 1.5.0
-assertjCoreVersion          = 3.21.0
-mockitoVersion              = 4.2.0
-
-# build dependencies
-jacocoVersion               = 0.8.7
-grgitVersion                = 4.1.1
-nexusPublishPluginVersion   = 1.1.0
-versionsPluginVersion       = 0.40.0
+
+## Brings "com.github.ben-manes.caffeine:caffeine:2.x which fails with: void sun.misc.Unsafe.ensureClassInitialized(java.lang.Class)
+## Can be upgraded to:  implementation("com.github.ben-manes.caffeine:caffeine:3.2.3")  Kee the caffeineVersion indented.
+pug4jVersion                = 2.4.1
+    caffeineVersion         = 3.2.3
+    graalVmVersion          = 25.0.1
+    jnrPosixVersion         = 3.1.21
+    jffiVersion             = 1.3.14
+gsonVersion                 = 2.13.2
+hsqldbVersion               = 2.7.4
+jgitVersion                 = 7.4.0.202509020913-r
+jsoupVersion                = 1.21.2
+logbackVersion              = 1.5.21
+neo4jVersion                = 5.26.0
+orientDbVersion             = 3.2.47
+pebbleVersion               = 4.0.0
+picocli                     = 4.7.7
+slf4jVersion                = 2.0.17
+snakeYamlVersion            = 2.5
+thymeleafVersion            = 3.1.3.RELEASE
+
+# Testing dependencies
+junit5Version               = 6.0.1
+junit5SystemExtVersion      = 1.2.2
+mockkVersion                = 1.14.7
+kotestVersion               = 6.0.7
+
+# E2E test dependencies
+testcontainersVersion       = 2.0.2
+dockerJavaVersion           = 3.7.0
+jacksonVersion              = 2.20.1
+
+# Build dependencies
+jacocoVersion               = 0.8.14
+grgitVersion                = 5.3.3
+nexusPublishPluginVersion   = 2.0.0
+versionsPluginVersion       = 0.53.0
 optionalBaseVersion         = 7.0.0
-jreleaserVersion            = 0.10.0
-mavenPluginDevVersion       = 0.3.1
+jreleaserVersion            = 1.21.0
+mavenPluginDevVersion       = 1.0.0
+dokkaVersion                = 2.1.0
 
-# jbake-maven-plugin dependencies
-mavenVersion                = 3.8.4
-mavenAnnotationsVersion     = 3.6.2
-sparkVersion                = 2.9.3
+# jbake-maven-plugin dependencies (JDK 17 compatible versions)
+mavenVersion                = 3.9.11
+mavenAnnotationsVersion     = 3.15.2
+sparkVersion                = 2.9.4
 
 org.gradle.caching=true
 org.gradle.parallel=true
 org.gradle.configureondemand = true
 org.gradle.vfs.watch = true
-
diff --git a/gradle/application.gradle b/gradle/application.gradle
index 165c9bb04..30c26af9c 100644
--- a/gradle/application.gradle
+++ b/gradle/application.gradle
@@ -1,5 +1,7 @@
-mainClassName = "org.jbake.launcher.Main"
-applicationName = "jbake"
+application {
+    mainClass = "org.jbake.launcher.Main"
+    applicationName = "jbake"
+}
 
 def examplesBase = "$project.buildDir/examples"
 
@@ -14,12 +16,12 @@ def exampleRepositories = [
 //create clone and Zip Task for each repository
 exampleRepositories.each { name, repository ->
 
-    task "clone_${name}Repository"() {
+    tasks.register("cloneRepo_${name}") {
         group = "distribution"
-        description "Clone jbake ${name} example project repository"
+        description = "Clone example JBake project repository: ${name}"
 
         def repositoryName = "$examplesBase/$name"
-
+        file(repositoryName).deleteDir()
         outputs.dir repositoryName
 
         doLast {
@@ -34,7 +36,7 @@ exampleRepositories.each { name, repository ->
         archiveBaseName = name
         archiveFileName = "${archiveBaseName.get()}.zip"
 
-        from project.tasks.getByName("clone_${name}Repository").outputs
+        from project.tasks.getByName("cloneRepo_${name}").outputs
         exclude 'README.md'
         exclude 'LICENSE'
         exclude '.git'
@@ -55,7 +57,22 @@ distributions {
 }
 
 startScripts {
-    defaultJvmOpts = ['-XX:MaxDirectMemorySize=512m','-Djna.nosys=true']
+    // Maven equivalent: jbake-dist/pom.xml > jvmSettings / extraArguments
+    defaultJvmOpts = [
+        '-XX:MaxDirectMemorySize=512m',
+        '-Djna.nosys=true',
+        // OrientDB DEBUG logging - OrientDB uses Java Util Logging (JUL)
+        '-Dlog.console.level=FINEST',
+        '-Dlog.file.level=FINEST',
+        // Java 17+ compatibility for OrientDB
+        '--add-opens', 'java.base/java.lang=ALL-UNNAMED',
+        '--add-opens', 'java.base/java.lang.reflect=ALL-UNNAMED',
+        '--add-opens', 'java.base/java.io=ALL-UNNAMED',
+        '--add-opens', 'java.base/java.util=ALL-UNNAMED',
+        '--add-opens', 'java.base/java.util.concurrent=ALL-UNNAMED',
+        '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED',
+        '--add-opens', 'java.base/java.nio=ALL-UNNAMED'
+    ]
     classpath = files("lib","logging")
 
     /**
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2e6e5897b..ac57dd155 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/jbake-core/build.gradle b/jbake-core/build.gradle
index 626a11a6b..b83541ae8 100644
--- a/jbake-core/build.gradle
+++ b/jbake-core/build.gradle
@@ -1,9 +1,11 @@
+//file:noinspection GroovyAssignabilityCheck
 import java.time.format.DateTimeFormatter
 
 plugins {
     id "org.jbake.convention.java-common"
     id 'java-library'
-    id 'nebula.optional-base' version "$optionalBaseVersion"
+    id 'org.jetbrains.kotlin.jvm'
+    id 'io.kotest'
 }
 
 apply from: "$rootDir/gradle/maven-publishing.gradle"
@@ -16,13 +18,7 @@ publishing {
         mavenJava(MavenPublication) {
             pom {
                 name = "jbake-core"
-
-                licenses {
-                    license {
-                        name = 'The MIT License (MIT)'
-                        url = 'http://opensource.org/licenses/MIT'
-                    }
-                }
+                licenses { license { name = 'The MIT License (MIT)'; url = 'http://opensource.org/licenses/MIT' } }
             }
         }
     }
@@ -30,31 +26,63 @@ publishing {
 
 
 dependencies {
+    implementation(platform("com.fasterxml.jackson:jackson-bom:$jacksonVersion")) // BOMs are already in scripts/build.gradle, is it needed here?
     api "commons-io:commons-io:$commonsIoVersion"
     api "org.apache.commons:commons-configuration2:$commonsConfigurationVersion"
     implementation "commons-beanutils:commons-beanutils:$commonsBeanutilsVersion"
-    implementation "org.apache.commons:commons-vfs2:$commonsVfs2Version", optional
+    implementation "org.apache.commons:commons-vfs2:$commonsVfs2Version" // For watching the file changes.
     implementation "org.apache.commons:commons-lang3:$commonsLangVersion"
-    implementation("com.googlecode.json-simple:json-simple:$jsonSimpleVersion") {
-        exclude group: "junit", module: "junit"
-    }
-    implementation "com.orientechnologies:orientdb-core:$orientDbVersion"
-    api "org.asciidoctor:asciidoctorj:$asciidoctorjVersion", optional
-    api "org.codehaus.groovy:groovy:$groovyVersion", optional
-    api "org.codehaus.groovy:groovy-templates:$groovyVersion", optional
-    api "org.codehaus.groovy:groovy-dateutil:$groovyVersion", optional
-    api "org.freemarker:freemarker:$freemarkerVersion", optional
-    api "org.thymeleaf:thymeleaf:$thymeleafVersion", optional
-    api "de.neuland-bfi:jade4j:$jade4jVersion", optional
-    api "com.vladsch.flexmark:flexmark:$flexmarkVersion", optional
-    api "com.vladsch.flexmark:flexmark-profile-pegdown:$flexmarkVersion", optional
-    api "io.pebbletemplates:pebble:$pebbleVersion", optional
+    implementation("com.googlecode.json-simple:json-simple:$jsonSimpleVersion") { exclude group: "junit", module: "junit" }
+    implementation("com.fasterxml.jackson.core:jackson-databind")
+    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
     implementation "org.jsoup:jsoup:$jsoupVersion"
-    implementation "org.yaml:snakeyaml:$snakeYamlVersion", optional
+    compileOnly "org.yaml:snakeyaml:$snakeYamlVersion"
+
+    // Databases
+    implementation "com.orientechnologies:orientdb-core:$orientDbVersion"
+    implementation "org.hsqldb:hsqldb:$hsqldbVersion"
+    implementation "org.neo4j:neo4j:$neo4jVersion"
+
 
-    // cli specific dependencies
-    implementation "org.eclipse.jetty:jetty-server:$jettyServerVersion", optional
-    implementation "info.picocli:picocli:$picocli", optional
+    // Templating engines are optional dependencies.
+    compileOnly "com.vladsch.flexmark:flexmark-profile-pegdown:$flexmarkVersion"
+    compileOnly "com.vladsch.flexmark:flexmark:$flexmarkVersion"
+    compileOnly "de.neuland-bfi:jade4j:$jade4jVersion"
+    compileOnly "de.neuland-bfi:pug4j:$pug4jVersion"
+        compileOnly "com.github.ben-manes.caffeine:caffeine:$caffeineVersion" // To avoid ERROR void sun.misc.Unsafe.ensureClassInitialized(java.lang.Class)
+    compileOnly "io.pebbletemplates:pebble:$pebbleVersion"
+    compileOnly "org.asciidoctor:asciidoctorj:$asciidoctorjVersion"
+    compileOnly ("org.freemarker:freemarker:$freemarkerVersion")// { dependencies {''} }
+        compileOnly "no.api.freemarker:freemarker-java8:3.0.3"
+    compileOnly "org.thymeleaf:thymeleaf:$thymeleafVersion"
+    implementation "org.apache.groovy:groovy:$groovyVersion"
+    implementation "org.apache.groovy:groovy-templates:$groovyVersion"
+    implementation "org.apache.groovy:groovy-dateutil:$groovyVersion"
+
+    // CLI specific dependencies
+    compileOnly "org.eclipse.jetty:jetty-server:$jettyServerVersion"
+    implementation "info.picocli:picocli:$picocli"
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+
+    // Test dependencies need access to optional compile dependencies
+    testImplementation "com.vladsch.flexmark:flexmark-profile-pegdown:$flexmarkVersion"
+    testImplementation "com.vladsch.flexmark:flexmark:$flexmarkVersion"
+    testImplementation "de.neuland-bfi:jade4j:$jade4jVersion"
+    testImplementation "de.neuland-bfi:pug4j:$pug4jVersion"
+    testImplementation "info.picocli:picocli:$picocli"
+    testImplementation "io.kotest:kotest-framework-engine:$kotestVersion"
+    testImplementation "io.kotest:kotest-assertions-core:$kotestVersion"
+    testImplementation "io.kotest:kotest-runner-junit5-jvm:$kotestVersion"
+    testImplementation "io.kotest:kotest-property-jvm:$kotestVersion"
+    testImplementation "io.mockk:mockk:$mockkVersion"
+    testImplementation "io.pebbletemplates:pebble:$pebbleVersion"
+    testImplementation "org.apache.commons:commons-vfs2:$commonsVfs2Version"
+    testImplementation "org.asciidoctor:asciidoctorj:$asciidoctorjVersion"
+    testImplementation "org.eclipse.jetty:jetty-server:$jettyServerVersion"
+    testImplementation "org.freemarker:freemarker:$freemarkerVersion"
+        testImplementation "no.api.freemarker:freemarker-java8:3.0.3"
+    testImplementation "org.thymeleaf:thymeleaf:$thymeleafVersion"
+    testImplementation "org.yaml:snakeyaml:$snakeYamlVersion"
 }
 
 processResources {
@@ -64,3 +92,15 @@ processResources {
             gitHash: grgit.head().abbreviatedId
     }
 }
+repositories {
+    mavenCentral()
+}
+kotlin {
+    jvmToolchain(javaVersion.toInteger())
+}
+
+test {
+    useJUnitPlatform()
+    systemProperty 'jbake.builtClassesDir', sourceSets.main.output.classesDirs.asPath.split(':')[0]
+    systemProperty 'jbake.buildOutputDir', "build"
+}
diff --git a/jbake-core/pom.xml b/jbake-core/pom.xml
new file mode 100644
index 000000000..20f6059a5
--- /dev/null
+++ b/jbake-core/pom.xml
@@ -0,0 +1,158 @@
+
+
+    4.0.0
+
+    
+        ch.zizka.jbake
+        jbake-base
+        6.0.0-r1
+        ..
+    
+
+    jbake-core
+    jar
+    jbake-core
+    The core library of JBake
+
+    The MIT License (MIT)http://opensource.org/licenses/MIT
+
+    
+        UTF-8
+    
+
+    
+    
+        
+        commons-iocommons-io
+        org.apache.commonscommons-configuration2
+        org.slf4jjul-to-slf4j${version.slf4j}
+        
+        org.asciidoctorasciidoctorjtrue
+        org.apache.groovygroovytrue
+        org.apache.groovygroovy-templatestrue
+        org.apache.groovygroovy-dateutiltrue
+        org.freemarkerfreemarkertrue
+        no.api.freemarkerfreemarker-java83.0.3true
+        org.thymeleafthymeleaftrue
+        de.neuland-bfijade4jtrue
+        de.neuland-bfipug4jtrue
+            
+        com.vladsch.flexmarkflexmarktrue
+        com.vladsch.flexmarkflexmark-profile-pegdowntrue
+        io.pebbletemplatespebbletrue
+
+
+        
+        ch.qos.logbacklogback-classic${version.logback}
+        com.fasterxml.jackson.corejackson-databind
+        com.fasterxml.jackson.datatypejackson-datatype-jdk8
+        com.fasterxml.jackson.datatypejackson-datatype-jsr310
+        com.googlecode.json-simplejson-simplejunitjunit
+        com.orientechnologiesorientdb-core
+        commons-beanutilscommons-beanutils
+        info.picoclipicoclitrue
+        org.apache.commonscommons-lang3
+        org.apache.commonscommons-vfs2true
+        org.eclipse.jettyjetty-servertrue
+        org.hsqldbhsqldb
+        org.jetbrains.kotlinkotlin-stdlib-jdk8
+        org.jsoupjsoup
+        org.neo4jneo4j
+            
+                
+                org.neo4jneo4j-slf4j-provider
+            
+        
+        org.yamlsnakeyamltrue
+
+        
+        testio.kotestkotest-assertions-core-jvm${version.kotest}
+        testio.kotestkotest-property-jvm${version.kotest}
+        testio.kotestkotest-runner-junit5-jvm${version.kotest}
+        testio.mockkmockk-jvm${version.mockk}
+        testjunitjunit
+        testorg.itsallcodejunit5-system-extensions
+        testorg.junit.jupiterjunit-jupiter-api
+        testorg.junit.jupiterjunit-jupiter-engine
+    
+
+    
+        
+        
+            
+            
+                src/main/resources
+                true
+                **/*.zip
+                
+            
+            
+            
+                src/main/resources
+                false
+                **/*.zip
+            
+        
+        
+            
+                org.codehaus.mojobuild-helper-maven-plugin
+                
+                    generate-sourcesadd-sourcesrc/main/kotlin
+                    add-test-sourcegenerate-test-sourcesadd-test-sourcesrc/test/kotlin
+                
+            
+
+            
+                org.apache.maven.pluginsmaven-shade-plugin
+            
+
+            
+            
+                org.jetbrains.kotlinkotlin-maven-plugin
+                
+                    
+                        kotlin-compilecompilecompile
+                        
+                            17
+                            
+                                ${project.basedir}/src/main/kotlin
+                                
+                            
+                        
+                    
+                    
+                        kotlin-test-compiletest-compiletest-compile
+                        
+                            17
+                            
+                                ${project.basedir}/src/test/kotlin
+                                
+                            
+                        
+                    
+                
+                
+                    ${version.java}
+                
+            
+
+            
+            
+                org.jetbrains.dokkadokka-maven-plugin
+                
+                    
+                    dokka-javadoc-jarpackagejavadocJar
+                
+                
+                    
+                        ${project.basedir}/src/main/kotlin
+                    
+                
+            
+
+        
+    
+
diff --git a/jbake-core/src/main/java/org/jbake/app/Asset.java b/jbake-core/src/main/java/org/jbake/app/Asset.java
deleted file mode 100644
index 7eefa962f..000000000
--- a/jbake-core/src/main/java/org/jbake/app/Asset.java
+++ /dev/null
@@ -1,166 +0,0 @@
-package org.jbake.app;
-
-import org.apache.commons.configuration2.CompositeConfiguration;
-import org.apache.commons.io.FileUtils;
-import org.jbake.app.configuration.JBakeConfiguration;
-import org.jbake.app.configuration.JBakeConfigurationFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Deals with assets (static files such as css, js or image files).
- *
- * @author Jonathan Bullock jonbullock@gmail.com
- */
-public class Asset {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(Asset.class);
-    private final List errors = new LinkedList<>();
-    private final JBakeConfiguration config;
-
-    /**
-     * @param source      Source file for the asset
-     * @param destination Destination (target) directory for asset file
-     * @param config      Project configuration
-     * @deprecated Use {@link #Asset(JBakeConfiguration)} instead.
-     * Compatibility constructor.
-     * Creates an instance of Asset.
-     */
-    @Deprecated
-    public Asset(File source, File destination, CompositeConfiguration config) {
-        this.config = new JBakeConfigurationFactory().createDefaultJbakeConfiguration(source, destination, config);
-    }
-
-    /**
-     * Creates an instance of Asset.
-     *
-     * @param config The project configuration. @see{{@link JBakeConfiguration}}
-     */
-    public Asset(JBakeConfiguration config) {
-        this.config = config;
-    }
-
-    /**
-     * Copy all files from assets folder to destination folder
-     * read from configuration
-     */
-    public void copy() {
-        copy(config.getAssetFolder());
-    }
-
-    /**
-     * Copy all files from supplied path.
-     *
-     * @param path The starting path
-     */
-    public void copy(File path) {
-        FileFilter filter = new FileFilter() {
-            @Override
-            public boolean accept(File file) {
-                return (!config.getAssetIgnoreHidden() || !file.isHidden()) && (file.isFile() || FileUtil.directoryOnlyIfNotIgnored(file, config));
-            }
-        };
-        copy(path, config.getDestinationFolder(), filter);
-    }
-
-    /**
-     * Copy one asset file at a time.
-     *
-     * @param asset The asset file to copy
-     */
-    public void copySingleFile(File asset) {
-        try {
-            if ( !asset.isDirectory() ) {
-                String targetPath = config.getDestinationFolder().getCanonicalPath() + File.separatorChar + assetSubPath(asset);
-                LOGGER.info("Copying single asset file to [{}]", targetPath);
-                copyFile(asset, new File(targetPath));
-            } else {
-                LOGGER.info("Skip copying single asset file [{}]. Is a directory.", asset.getPath());
-            }
-        } catch (IOException io) {
-            LOGGER.error("Failed to copy the asset file.", io);
-        }
-    }
-
-    /**
-     * Determine if a given file is an asset file.
-     * @param path to the file to validate.
-     * @return true if the path provided points to a file in the asset folder.
-     */
-    public boolean isAssetFile(File path) {
-        boolean isAsset = false;
-
-        try {
-            if(FileUtil.directoryOnlyIfNotIgnored(path.getParentFile(), config)) {
-                if (FileUtil.isFileInDirectory(path, config.getAssetFolder())) {
-                    isAsset = true;
-                } else if (FileUtil.isFileInDirectory(path, config.getContentFolder())
-                    && FileUtil.getNotContentFileFilter(config).accept(path)) {
-                    isAsset = true;
-                }
-            }
-        } catch (IOException ioe) {
-            LOGGER.error("Unable to determine the path to asset file {}", path.getPath(), ioe);
-        }
-        return isAsset;
-    }
-
-    /**
-     * Responsible for copying any asset files that exist within the content directory.
-     *
-     * @param path of the content directory
-     */
-    public void copyAssetsFromContent(File path) {
-        copy(path, config.getDestinationFolder(), FileUtil.getNotContentFileFilter(config));
-    }
-
-    /**
-     * Accessor method to the collection of errors generated during the bake
-     *
-     * @return a list of errors.
-     */
-    public List getErrors() {
-        return new ArrayList<>(errors);
-    }
-
-    private String assetSubPath(File asset) throws IOException {
-        // First, strip asset folder from file path
-        String targetFolder = asset.getCanonicalPath().replace(config.getAssetFolder().getCanonicalPath() + File.separatorChar, "");
-        // And just to be sure, let's also remove the content folder, as some assets are copied from here.
-        targetFolder = targetFolder.replace(config.getContentFolder().getCanonicalPath() + File.separatorChar, "");
-        return targetFolder;
-    }
-
-    private void copy(File sourceFolder, File targetFolder, final FileFilter filter) {
-        final File[] assets = sourceFolder.listFiles(filter);
-        if (assets != null) {
-            Arrays.sort(assets);
-            for (File asset : assets) {
-                final File target = new File(targetFolder, asset.getName());
-                if (asset.isFile()) {
-                    copyFile(asset, target);
-                } else if (asset.isDirectory()) {
-                    copy(asset, target, filter);
-                }
-            }
-        }
-    }
-
-    private void copyFile(File asset, File targetFolder) {
-        try {
-            FileUtils.copyFile(asset, targetFolder);
-            LOGGER.info("Copying [{}]... done!", asset.getPath());
-        } catch (IOException|IllegalArgumentException e) {
-            LOGGER.error("Copying [{}]... failed!", asset.getPath(), e);
-            errors.add(e);
-        }
-    }
-}
diff --git a/jbake-core/src/main/java/org/jbake/app/ContentStore.java b/jbake-core/src/main/java/org/jbake/app/ContentStore.java
deleted file mode 100644
index 352e97ae5..000000000
--- a/jbake-core/src/main/java/org/jbake/app/ContentStore.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright 2015 jdlee.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package org.jbake.app;
-
-import com.orientechnologies.common.log.OLogManager;
-import com.orientechnologies.orient.core.Orient;
-import com.orientechnologies.orient.core.db.ODatabaseSession;
-import com.orientechnologies.orient.core.db.ODatabaseType;
-import com.orientechnologies.orient.core.db.OrientDB;
-import com.orientechnologies.orient.core.db.OrientDBConfig;
-import com.orientechnologies.orient.core.metadata.schema.OClass;
-import com.orientechnologies.orient.core.metadata.schema.OSchema;
-import com.orientechnologies.orient.core.metadata.schema.OType;
-import com.orientechnologies.orient.core.record.OElement;
-import com.orientechnologies.orient.core.sql.executor.OResultSet;
-import org.jbake.model.DocumentModel;
-import org.jbake.model.DocumentTypes;
-import org.jbake.model.ModelAttributes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * @author jdlee
- */
-public class ContentStore {
-
-    private static final String STATEMENT_GET_PUBLISHED_POST_BY_TYPE_AND_TAG = "select * from Documents where status='published' and type='%s' and ? in tags order by date desc";
-    private static final String STATEMENT_GET_DOCUMENT_STATUS_BY_DOCTYPE_AND_URI = "select sha1,rendered from Documents where sourceuri=?";
-    private static final String STATEMENT_GET_PUBLISHED_COUNT = "select count(*) as count from Documents where status='published' and type='%s'";
-    private static final String STATEMENT_MARK_CONTENT_AS_RENDERD = "update Documents set rendered=true where rendered=false and type='%s' and sourceuri='%s' and cached=true";
-    private static final String STATEMENT_DELETE_DOCTYPE_BY_SOURCEURI = "delete from Documents where sourceuri=?";
-    private static final String STATEMENT_GET_UNDRENDERED_CONTENT = "select * from Documents where rendered=false order by date desc";
-    private static final String STATEMENT_GET_SIGNATURE_FOR_TEMPLATES = "select sha1 from Signatures where key='templates'";
-    private static final String STATEMENT_GET_TAGS_FROM_PUBLISHED_POSTS = "select tags from Documents where status='published' and type='post'";
-    private static final String STATEMENT_GET_ALL_CONTENT_BY_DOCTYPE = "select * from Documents where type='%s' order by date desc";
-    private static final String STATEMENT_GET_PUBLISHED_CONTENT_BY_DOCTYPE = "select * from Documents where status='published' and type='%s' order by date desc";
-    private static final String STATEMENT_GET_PUBLISHED_POSTS_BY_TAG = "select * from Documents where status='published' and type='post' and ? in tags order by date desc";
-    private static final String STATEMENT_GET_TAGS_BY_DOCTYPE = "select tags from Documents where status='published' and type='%s'";
-    private static final String STATEMENT_INSERT_TEMPLATES_SIGNATURE = "insert into Signatures(key,sha1) values('templates',?)";
-    private static final String STATEMENT_DELETE_ALL = "delete from Documents where type='%s'";
-    private static final String STATEMENT_UPDATE_TEMPLATE_SIGNATURE = "update Signatures set sha1=? where key='templates'";
-    private static final String STATEMENT_GET_DOCUMENT_COUNT_BY_TYPE = "select count(*) as count from Documents where type='%s'";
-
-    private final Logger logger = LoggerFactory.getLogger(ContentStore.class);
-    private final String type;
-    private final String name;
-
-    private ODatabaseSession db;
-
-    private long start = -1;
-    private long limit = -1;
-    private OrientDB orient;
-
-    public ContentStore(final String type, String name) {
-        this.type = type;
-        this.name = name;
-    }
-
-
-    public void startup() {
-        startupIfEnginesAreMissing();
-
-        if (type.equalsIgnoreCase(ODatabaseType.PLOCAL.name())) {
-            orient = new OrientDB(type + ":" + name, OrientDBConfig.defaultConfig());
-        } else {
-            orient = new OrientDB(type + ":", OrientDBConfig.defaultConfig());
-        }
-
-        orient.createIfNotExists(name, ODatabaseType.valueOf(type.toUpperCase()));
-
-        db = orient.open(name, "admin", "admin");
-
-        activateOnCurrentThread();
-
-        updateSchema();
-    }
-
-    public long getStart() {
-        return start;
-    }
-
-    public void setStart(int start) {
-        this.start = start;
-    }
-
-    public long getLimit() {
-        return limit;
-    }
-
-    public void setLimit(int limit) {
-        this.limit = limit;
-    }
-
-    public void resetPagination() {
-        this.start = -1;
-        this.limit = -1;
-    }
-
-    public final void updateSchema() {
-
-        OSchema schema = db.getMetadata().getSchema();
-
-        if (!schema.existsClass(Schema.DOCUMENTS)) {
-            createDocType(schema);
-        }
-        if (!schema.existsClass(Schema.SIGNATURES)) {
-            createSignatureType(schema);
-        }
-    }
-
-    public void close() {
-        if (db != null) {
-            activateOnCurrentThread();
-            db.close();
-        }
-
-        if (orient != null) {
-            orient.close();
-        }
-        DBUtil.closeDataStore();
-    }
-
-    public void shutdown() {
-
-//        Orient.instance().shutdown();
-    }
-
-    private void startupIfEnginesAreMissing() {
-        // Using a jdk which doesn't bundle a javascript engine
-        // throws a NoClassDefFoundError while logging the warning
-        // see https://github.com/orientechnologies/orientdb/issues/5855
-        OLogManager.instance().setWarnEnabled(false);
-
-        // If an instance of Orient was previously shutdown all engines are removed.
-        // We need to startup Orient again.
-        if (Orient.instance().getEngines().isEmpty()) {
-            Orient.instance().startup();
-        }
-        OLogManager.instance().setWarnEnabled(true);
-    }
-
-    public void drop() {
-        activateOnCurrentThread();
-//        db.drop();
-
-        orient.drop(name);
-    }
-
-    private void activateOnCurrentThread() {
-        if (db != null) {
-            db.activateOnCurrentThread();
-        } else {
-            System.out.println("db is null on activate");
-        }
-    }
-
-    public long getDocumentCount(String docType) {
-        activateOnCurrentThread();
-        String statement = String.format(STATEMENT_GET_DOCUMENT_COUNT_BY_TYPE, docType);
-        return (long) query(statement).get(0).get("count");
-    }
-
-    public long getPublishedCount(String docType) {
-        String statement = String.format(STATEMENT_GET_PUBLISHED_COUNT, docType);
-        return (long) query(statement).get(0).get("count");
-    }
-
-    public DocumentList getDocumentByUri(String uri) {
-        return query("select * from Documents where sourceuri=?", uri);
-    }
-
-    public DocumentList getDocumentStatus(String uri) {
-        return query(STATEMENT_GET_DOCUMENT_STATUS_BY_DOCTYPE_AND_URI, uri);
-    }
-
-    public DocumentList getPublishedPosts() {
-        return getPublishedContent("post");
-    }
-
-    public DocumentList getPublishedPosts(boolean applyPaging) {
-        return getPublishedContent("post", applyPaging);
-    }
-
-    public DocumentList getPublishedPostsByTag(String tag) {
-        return query(STATEMENT_GET_PUBLISHED_POSTS_BY_TAG, tag);
-    }
-
-    public DocumentList getPublishedDocumentsByTag(String tag) {
-        final DocumentList documents = new DocumentList<>();
-
-        for (final String docType : DocumentTypes.getDocumentTypes()) {
-            String statement = String.format(STATEMENT_GET_PUBLISHED_POST_BY_TYPE_AND_TAG, docType);
-            DocumentList documentsByTag = query(statement, tag);
-            documents.addAll(documentsByTag);
-        }
-        return documents;
-    }
-
-    public DocumentList getPublishedPages() {
-        return getPublishedContent("page");
-    }
-
-    public DocumentList getPublishedContent(String docType) {
-        return getPublishedContent(docType, false);
-    }
-
-    private DocumentList getPublishedContent(String docType, boolean applyPaging) {
-        String query = String.format(STATEMENT_GET_PUBLISHED_CONTENT_BY_DOCTYPE, docType);
-        if (applyPaging && hasStartAndLimitBoundary()) {
-            query += " SKIP " + start + " LIMIT " + limit;
-        }
-        return query(query);
-    }
-
-    public DocumentList getAllContent(String docType) {
-        return getAllContent(docType, false);
-    }
-
-    public DocumentList getAllContent(String docType, boolean applyPaging) {
-        String query = String.format(STATEMENT_GET_ALL_CONTENT_BY_DOCTYPE, docType);
-        if (applyPaging && hasStartAndLimitBoundary()) {
-            query += " SKIP " + start + " LIMIT " + limit;
-        }
-        return query(query);
-    }
-
-    private boolean hasStartAndLimitBoundary() {
-        return (start >= 0) && (limit > -1);
-    }
-
-    private DocumentList getAllTagsFromPublishedPosts() {
-        return query(STATEMENT_GET_TAGS_FROM_PUBLISHED_POSTS);
-    }
-
-    private DocumentList getSignaturesForTemplates() {
-        return query(STATEMENT_GET_SIGNATURE_FOR_TEMPLATES);
-    }
-
-    public DocumentList getUnrenderedContent() {
-        return query(STATEMENT_GET_UNDRENDERED_CONTENT);
-    }
-
-    public void deleteContent(String uri) {
-        executeCommand(STATEMENT_DELETE_DOCTYPE_BY_SOURCEURI, uri);
-    }
-
-    public void markContentAsRendered(DocumentModel document) {
-        String statement = String.format(STATEMENT_MARK_CONTENT_AS_RENDERD, document.getType(), document.getSourceuri());
-        executeCommand(statement);
-    }
-
-    private void updateSignatures(String currentTemplatesSignature) {
-        executeCommand(STATEMENT_UPDATE_TEMPLATE_SIGNATURE, currentTemplatesSignature);
-    }
-
-    public void deleteAllByDocType(String docType) {
-        String statement = String.format(STATEMENT_DELETE_ALL, docType);
-        executeCommand(statement);
-    }
-
-    private void insertTemplatesSignature(String currentTemplatesSignature) {
-        executeCommand(STATEMENT_INSERT_TEMPLATES_SIGNATURE, currentTemplatesSignature);
-    }
-
-    private DocumentList query(String sql) {
-        activateOnCurrentThread();
-        OResultSet results = db.query(sql);
-        return DocumentList.wrap(results);
-    }
-
-    private DocumentList query(String sql, Object... args) {
-        activateOnCurrentThread();
-        OResultSet results = db.command(sql, args);
-        return DocumentList.wrap(results);
-    }
-
-    private void executeCommand(String query, Object... args) {
-        activateOnCurrentThread();
-        db.command(query, args);
-    }
-
-    public Set getTags() {
-        DocumentList docs = this.getAllTagsFromPublishedPosts();
-        Set result = new HashSet<>();
-        for (DocumentModel document : docs) {
-            String[] tags = document.getTags();
-            Collections.addAll(result, tags);
-        }
-        return result;
-    }
-
-    public Set getAllTags() {
-        Set result = new HashSet<>();
-        for (String docType : DocumentTypes.getDocumentTypes()) {
-            String statement = String.format(STATEMENT_GET_TAGS_BY_DOCTYPE, docType);
-            DocumentList docs = query(statement);
-            for (DocumentModel document : docs) {
-                String[] tags = document.getTags();
-                Collections.addAll(result, tags);
-            }
-        }
-        return result;
-    }
-
-    private void createDocType(final OSchema schema) {
-        logger.debug("Create document class");
-
-        OClass page = schema.createClass(Schema.DOCUMENTS);
-        page.createProperty(ModelAttributes.SHA1, OType.STRING).setNotNull(true);
-        page.createIndex(Schema.DOCUMENTS + "sha1Index", OClass.INDEX_TYPE.NOTUNIQUE, ModelAttributes.SHA1);
-        page.createProperty(ModelAttributes.SOURCE_URI, OType.STRING).setNotNull(true);
-        page.createIndex(Schema.DOCUMENTS + "sourceUriIndex", OClass.INDEX_TYPE.UNIQUE, ModelAttributes.SOURCE_URI);
-        page.createProperty(ModelAttributes.CACHED, OType.BOOLEAN).setNotNull(true);
-        page.createIndex(Schema.DOCUMENTS + "cachedIndex", OClass.INDEX_TYPE.NOTUNIQUE, ModelAttributes.CACHED);
-        page.createProperty(ModelAttributes.RENDERED, OType.BOOLEAN).setNotNull(true);
-        page.createIndex(Schema.DOCUMENTS + "renderedIndex", OClass.INDEX_TYPE.NOTUNIQUE, ModelAttributes.RENDERED);
-        page.createProperty(ModelAttributes.STATUS, OType.STRING).setNotNull(true);
-        page.createIndex(Schema.DOCUMENTS + "statusIndex", OClass.INDEX_TYPE.NOTUNIQUE, ModelAttributes.STATUS);
-        page.createProperty(ModelAttributes.TYPE, OType.STRING).setNotNull(true);
-        page.createIndex(Schema.DOCUMENTS + "typeIndex", OClass.INDEX_TYPE.NOTUNIQUE, ModelAttributes.TYPE);
-
-    }
-
-    private void createSignatureType(OSchema schema) {
-        OClass signatures = schema.createClass(Schema.SIGNATURES);
-        signatures.createProperty(ModelAttributes.SHA1, OType.STRING).setNotNull(true);
-        signatures.createIndex("sha1Idx", OClass.INDEX_TYPE.UNIQUE, ModelAttributes.SHA1);
-    }
-
-    public void updateAndClearCacheIfNeeded(boolean needed, File templateFolder) {
-
-        boolean clearCache = needed;
-
-        if (!needed) {
-            clearCache = updateTemplateSignatureIfChanged(templateFolder);
-        }
-
-        if (clearCache) {
-            deleteAllDocumentTypes();
-            this.updateSchema();
-        }
-    }
-
-    private boolean updateTemplateSignatureIfChanged(File templateFolder) {
-        boolean templateSignatureChanged = false;
-
-        DocumentList docs = this.getSignaturesForTemplates();
-        String currentTemplatesSignature;
-        try {
-            currentTemplatesSignature = FileUtil.sha1(templateFolder);
-        } catch (Exception e) {
-            currentTemplatesSignature = "";
-        }
-        if (!docs.isEmpty()) {
-            String sha1 = docs.get(0).getSha1();
-            if (!sha1.equals(currentTemplatesSignature)) {
-                this.updateSignatures(currentTemplatesSignature);
-                templateSignatureChanged = true;
-            }
-        } else {
-            // first computation of templates signature
-            this.insertTemplatesSignature(currentTemplatesSignature);
-            templateSignatureChanged = true;
-        }
-        return templateSignatureChanged;
-    }
-
-    private void deleteAllDocumentTypes() {
-        for (String docType : DocumentTypes.getDocumentTypes()) {
-            try {
-                this.deleteAllByDocType(docType);
-            } catch (Exception e) {
-                // maybe a non existing document type
-            }
-        }
-    }
-
-    public boolean isActive() {
-        return db.isActiveOnCurrentThread();
-    }
-
-    public void addDocument(DocumentModel document) {
-        OElement element = db.newElement(Schema.DOCUMENTS);
-        document.forEach((k, v) -> element.setProperty(k, v, OType.ANY));
-        element.save();
-    }
-
-    protected abstract class Schema {
-        static final String DOCUMENTS = "Documents";
-        static final String SIGNATURES = "Signatures";
-    }
-
-}
diff --git a/jbake-core/src/main/java/org/jbake/app/Crawler.java b/jbake-core/src/main/java/org/jbake/app/Crawler.java
deleted file mode 100644
index 0c3d8b9d6..000000000
--- a/jbake-core/src/main/java/org/jbake/app/Crawler.java
+++ /dev/null
@@ -1,325 +0,0 @@
-package org.jbake.app;
-
-import com.orientechnologies.orient.core.record.impl.ODocument;
-import org.apache.commons.configuration2.CompositeConfiguration;
-import org.apache.commons.io.FilenameUtils;
-import org.jbake.app.configuration.JBakeConfiguration;
-import org.jbake.app.configuration.JBakeConfigurationFactory;
-import org.jbake.model.DocumentModel;
-import org.jbake.model.DocumentStatus;
-import org.jbake.model.DocumentTypes;
-import org.jbake.model.ModelAttributes;
-import org.jbake.util.HtmlUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Map;
-
-/**
- * Crawls a file system looking for content.
- *
- * @author Jonathan Bullock jonbullock@gmail.com
- */
-public class Crawler {
-
-    private static final Logger logger = LoggerFactory.getLogger(Crawler.class);
-    private final ContentStore db;
-    private final JBakeConfiguration config;
-    private final Parser parser;
-
-    /**
-     * @param db     Database instance for content
-     * @param source Base directory where content directory is located
-     * @param config Project configuration
-     * @deprecated Use {@link #Crawler(ContentStore, JBakeConfiguration)} instead.
-     * 

- * Creates new instance of Crawler. - */ - @Deprecated - public Crawler(ContentStore db, File source, CompositeConfiguration config) { - this.db = db; - this.config = new JBakeConfigurationFactory().createDefaultJbakeConfiguration(source, config); - this.parser = new Parser(this.config); - } - - /** - * Creates new instance of Crawler. - * - * @param db Database instance for content - * @param config Project configuration - */ - public Crawler(ContentStore db, JBakeConfiguration config) { - this.db = db; - this.config = config; - this.parser = new Parser(config); - } - - public void crawl() { - crawl(config.getContentFolder()); - - logger.info("Content detected:"); - for (String docType : DocumentTypes.getDocumentTypes()) { - long count = db.getDocumentCount(docType); - if (count > 0) { - logger.info("Parsed {} files of type: {}", count, docType); - } - } - } - - public void crawlDataFiles() { - crawlDataFiles(config.getDataFolder()); - - logger.info("Data files detected:"); - String docType = config.getDataFileDocType(); - long count = db.getDocumentCount(docType); - if (count > 0) { - logger.info("Parsed {} files", count); - } - } - - /** - * Crawl all files and folders looking for content. - * - * @param path Folder to start from - */ - private void crawl(File path) { - File[] contents = path.listFiles(FileUtil.getFileFilter(config)); - if (contents != null) { - Arrays.sort(contents); - for (File sourceFile : contents) { - if (sourceFile.isFile()) { - crawlFile(sourceFile); - } else if (sourceFile.isDirectory()) { - crawl(sourceFile); - } - } - } - } - - private void crawlFile(File sourceFile) { - - StringBuilder sb = new StringBuilder(); - sb.append("Processing [").append(sourceFile.getPath()).append("]... "); - String sha1 = buildHash(sourceFile); - String uri = buildURI(sourceFile); - DocumentStatus status = findDocumentStatus(uri, sha1); - if (status == DocumentStatus.UPDATED) { - sb.append(" : modified "); - db.deleteContent(uri); - } else if (status == DocumentStatus.IDENTICAL) { - sb.append(" : same "); - } else if (DocumentStatus.NEW == status) { - sb.append(" : new "); - } - - logger.info("{}", sb); - - if (status != DocumentStatus.IDENTICAL) { - processSourceFile(sourceFile, sha1, uri); - } - } - - /** - * Crawl all files and folders looking for data files. - * - * @param path Folder to start from - */ - private void crawlDataFiles(File path) { - File[] contents = path.listFiles(FileUtil.getDataFileFilter()); - if (contents != null) { - Arrays.sort(contents); - for (File sourceFile : contents) { - if (sourceFile.isFile()) { - StringBuilder sb = new StringBuilder(); - sb.append("Processing [").append(sourceFile.getPath()).append("]... "); - String sha1 = buildHash(sourceFile); - String uri = buildDataFileURI(sourceFile); - boolean process = true; - DocumentStatus status = DocumentStatus.NEW; - String docType = config.getDataFileDocType(); - status = findDocumentStatus(uri, sha1); - if (status == DocumentStatus.UPDATED) { - sb.append(" : modified "); - db.deleteContent(uri); - } else if (status == DocumentStatus.IDENTICAL) { - sb.append(" : same "); - process = false; - } - if (!process) { - break; - } - if (DocumentStatus.NEW == status) { - sb.append(" : new "); - } - if (process) { // new or updated - crawlDataFile(sourceFile, sha1, uri, docType); - } - logger.info("{}", sb); - } - if (sourceFile.isDirectory()) { - crawlDataFiles(sourceFile); - } - } - } - } - - private String buildHash(final File sourceFile) { - String sha1; - try { - sha1 = FileUtil.sha1(sourceFile); - } catch (Exception e) { - logger.error("unable to build sha1 hash for source file '{}'", sourceFile); - sha1 = ""; - } - return sha1; - } - - private String buildURI(final File sourceFile) { - String uri = FileUtil.asPath(sourceFile).replace(FileUtil.asPath(config.getContentFolder()), ""); - - if (useNoExtensionUri(uri)) { - // convert URI from xxx.html to xxx/index.html - uri = createNoExtensionUri(uri); - } else { - uri = createUri(uri); - } - - // strip off leading / to enable generating non-root based sites - if (uri.startsWith(FileUtil.URI_SEPARATOR_CHAR)) { - uri = uri.substring(1); - } - - return uri; - } - - private String buildDataFileURI(final File sourceFile) { - String uri = FileUtil.asPath(sourceFile).replace(FileUtil.asPath(config.getDataFolder()), ""); - // strip off leading / - if (uri.startsWith(FileUtil.URI_SEPARATOR_CHAR)) { - uri = uri.substring(1, uri.length()); - } - return uri; - } - - // TODO: Refactor - parametrize the following two methods into one. - // commons-codec's URLCodec could be used when we add that dependency. - private String createUri(String uri) { - try { - return FileUtil.URI_SEPARATOR_CHAR - + FilenameUtils.getPath(uri) - + URLEncoder.encode(FilenameUtils.getBaseName(uri), StandardCharsets.UTF_8.name()) - + config.getOutputExtension(); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Missing UTF-8 encoding??", e); // Won't happen unless JDK is broken. - } - } - - private String createNoExtensionUri(String uri) { - try { - return FileUtil.URI_SEPARATOR_CHAR - + FilenameUtils.getPath(uri) - + URLEncoder.encode(FilenameUtils.getBaseName(uri), StandardCharsets.UTF_8.name()) - + FileUtil.URI_SEPARATOR_CHAR - + "index" - + config.getOutputExtension(); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Missing UTF-8 encoding??", e); // Won't happen unless JDK is broken. - } - } - - private boolean useNoExtensionUri(String uri) { - boolean noExtensionUri = config.getUriWithoutExtension(); - String noExtensionUriPrefix = config.getPrefixForUriWithoutExtension(); - - return noExtensionUri - && (noExtensionUriPrefix != null) - && (noExtensionUriPrefix.length() > 0) - && uri.startsWith(noExtensionUriPrefix); - } - - private void crawlDataFile(final File sourceFile, final String sha1, final String uri, final String documentType) { - try { - DocumentModel document = parser.processFile(sourceFile); - if (document != null) { - document.setSha1(sha1); - document.setRendered(true); - document.setFile(sourceFile.getPath()); - document.setSourceUri(uri); - document.setType(documentType); - - db.addDocument(document); - } else { - logger.warn("{} couldn't be parsed so it has been ignored!", sourceFile); - } - } catch (Exception ex) { - throw new RuntimeException("Failed crawling file: " + sourceFile.getPath() + " " + ex.getMessage(), ex); - } - } - - private void processSourceFile(final File sourceFile, final String sha1, final String uri) { - DocumentModel document = parser.processFile(sourceFile); - - if (document != null) { - if (DocumentTypes.contains(document.getType())) { - addAdditionalDocumentAttributes(document, sourceFile, sha1, uri); - - if (config.getImgPathUpdate()) { - // Prevent image source url's from breaking - HtmlUtil.fixImageSourceUrls(document, config); - } - - db.addDocument(document); - } else { - logger.warn("{} has an unknown document type '{}' and has been ignored!", sourceFile, document.getType()); - } - } else { - logger.warn("{} has an invalid header, it has been ignored!", sourceFile); - } - } - - private void addAdditionalDocumentAttributes(DocumentModel document, File sourceFile, String sha1, String uri) { - document.setRootPath(getPathToRoot(sourceFile)); - document.setSha1(sha1); - document.setRendered(false); - document.setFile(sourceFile.getPath()); - document.setSourceUri(uri); - document.setUri(uri); - document.setCached(true); - - if (document.getStatus().equals(ModelAttributes.Status.PUBLISHED_DATE) - && (document.getDate() != null) - && new Date().after(document.getDate())) { - document.setStatus(ModelAttributes.Status.PUBLISHED); - } - - if (config.getUriWithoutExtension()) { - document.setNoExtensionUri(uri.replace("/index.html", "/")); - } - } - - private String getPathToRoot(File sourceFile) { - return FileUtil.getUriPathToContentRoot(config, sourceFile); - } - - private DocumentStatus findDocumentStatus(String uri, String sha1) { - DocumentList match = db.getDocumentStatus(uri); - if (!match.isEmpty()) { - DocumentModel document = match.get(0); - String oldHash = document.getSha1(); - if (!oldHash.equals(sha1) || !document.getRendered()) { - return DocumentStatus.UPDATED; - } else { - return DocumentStatus.IDENTICAL; - } - } else { - return DocumentStatus.NEW; - } - } - -} diff --git a/jbake-core/src/main/java/org/jbake/app/DBUtil.java b/jbake-core/src/main/java/org/jbake/app/DBUtil.java deleted file mode 100644 index 33568b450..000000000 --- a/jbake-core/src/main/java/org/jbake/app/DBUtil.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.jbake.app; - -import com.orientechnologies.orient.core.db.record.OTrackedList; -import com.orientechnologies.orient.core.record.OElement; -import com.orientechnologies.orient.core.sql.executor.OResult; -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.model.DocumentModel; - -import java.util.ArrayList; - -public class DBUtil { - private static ContentStore contentStore; - - @Deprecated - public static ContentStore createDataStore(final String type, String name) { - if (contentStore == null) { - contentStore = new ContentStore(type, name); - } - return contentStore; - } - - @Deprecated - public static void updateSchema(final ContentStore db) { - db.updateSchema(); - } - - public static ContentStore createDataStore(JBakeConfiguration configuration) { - if (contentStore == null) { - contentStore = new ContentStore(configuration.getDatabaseStore(), configuration.getDatabasePath()); - } - - return contentStore; - } - - public static void closeDataStore() { - contentStore = null; - } - - public static DocumentModel documentToModel(OResult doc) { - DocumentModel result = new DocumentModel(); - - for (String key : doc.getPropertyNames()) { - result.put(key, doc.getProperty(key)); - } - return result; - } - - /** - * Converts a DB list into a String array - * - * @param entry Entry input to be converted - * @return input entry as String[] - */ - @SuppressWarnings("unchecked") - public static String[] toStringArray(Object entry) { - if (entry instanceof String[]) { - return (String[]) entry; - } else if (entry instanceof OTrackedList) { - OTrackedList list = (OTrackedList) entry; - return list.toArray(new String[list.size()]); - } else if (entry instanceof ArrayList) { - ArrayList list = (ArrayList) entry; - return list.toArray(new String[list.size()]); - } - return new String[0]; - } - -} diff --git a/jbake-core/src/main/java/org/jbake/app/DocumentList.java b/jbake-core/src/main/java/org/jbake/app/DocumentList.java deleted file mode 100644 index d297ff387..000000000 --- a/jbake-core/src/main/java/org/jbake/app/DocumentList.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.jbake.app; - -import com.orientechnologies.orient.core.sql.executor.OResult; -import com.orientechnologies.orient.core.sql.executor.OResultSet; -import org.jbake.model.DocumentModel; - -import java.util.LinkedList; - -/** - * Wraps an OrientDB document iterator into a model usable by - * template engines. - * - * @author Cédric Champeau - */ -public class DocumentList extends LinkedList { - - public static DocumentList wrap(OResultSet docs) { - DocumentList list = new DocumentList<>(); - while (docs.hasNext()) { - OResult next = docs.next(); - list.add(DBUtil.documentToModel(next)); - } - docs.close(); - return list; - } - -} diff --git a/jbake-core/src/main/java/org/jbake/app/FileUtil.java b/jbake-core/src/main/java/org/jbake/app/FileUtil.java deleted file mode 100644 index c80dbf9e2..000000000 --- a/jbake-core/src/main/java/org/jbake/app/FileUtil.java +++ /dev/null @@ -1,333 +0,0 @@ -package org.jbake.app; - -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.parser.Engines; - -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.net.URLDecoder; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.MessageDigest; - -/** - * Provides File related functions - * - * @author Jonathan Bullock jonbullock@gmail.com - */ -public class FileUtil { - - public static final String URI_SEPARATOR_CHAR = "/"; - - /** - * Filters files based on their file extension. - * - * @param config the jbake configuration - * @return Object for filtering files - */ - public static FileFilter getFileFilter(JBakeConfiguration config) { - return new FileFilter() { - - @Override - public boolean accept(File pathname) { - //Accept if input is a non-hidden file with registered extension - //or if a non-hidden and not-ignored directory - return !pathname.isHidden() && (pathname.isFile() - && Engines.getRecognizedExtensions().contains(fileExt(pathname))) || (directoryOnlyIfNotIgnored(pathname, config)); - } - }; - } - - /** - * Filters files based on their file extension. - * - * @return Object for filtering files - * @deprecated use {@link #getFileFilter(JBakeConfiguration)} instead - */ - @Deprecated - public static FileFilter getFileFilter() { - return new FileFilter() { - - @Override - public boolean accept(File pathname) { - //Accept if input is a non-hidden file with registered extension - //or if a non-hidden and not-ignored directory - return !pathname.isHidden() && (pathname.isFile() - && Engines.getRecognizedExtensions().contains(fileExt(pathname))) || (directoryOnlyIfNotIgnored(pathname)); - } - }; - } - - /** - * Filters files based on their file extension - only find data files (i.e. files with .yaml or .yml extension) - * - * @return Object for filtering files - */ - public static FileFilter getDataFileFilter() { - return new FileFilter() { - - @Override - public boolean accept(File pathname) { - return "yaml".equalsIgnoreCase(fileExt(pathname)) || "yml".equalsIgnoreCase(fileExt(pathname)); - } - }; - } - - /** - * Gets the list of files that are not content files based on their extension. - * - * @param config the jbake configuration - * @return FileFilter object - */ - public static FileFilter getNotContentFileFilter(JBakeConfiguration config) { - return new FileFilter() { - - @Override - public boolean accept(File pathname) { - //Accept if input is a non-hidden file with NOT-registered extension - //or if a non-hidden and not-ignored directory - return !pathname.isHidden() && (pathname.isFile() - //extension should not be from registered content extensions - && !Engines.getRecognizedExtensions().contains(fileExt(pathname))) - || (directoryOnlyIfNotIgnored(pathname, config)); - } - }; - } - - /** - * Gets the list of files that are not content files based on their extension. - * - * @return FileFilter object - * @deprecated use {@link #getNotContentFileFilter(JBakeConfiguration)} instead - */ - @Deprecated - public static FileFilter getNotContentFileFilter() { - return new FileFilter() { - - @Override - public boolean accept(File pathname) { - //Accept if input is a non-hidden file with NOT-registered extension - //or if a non-hidden and not-ignored directory - return !pathname.isHidden() && (pathname.isFile() - //extension should not be from registered content extensions - && !Engines.getRecognizedExtensions().contains(fileExt(pathname))) - || (directoryOnlyIfNotIgnored(pathname)); - } - }; - } - - /** - * Ignores directory (and children) if it contains a file named in the - * configuration as a marker to ignore the directory. - * - * @param file the file to test - * @param config the jbake configuration - * @return true if file is directory and not ignored - */ - public static boolean directoryOnlyIfNotIgnored(File file, JBakeConfiguration config) { - boolean accept = false; - - FilenameFilter ignoreFile = new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.equalsIgnoreCase(config.getIgnoreFileName()); - } - }; - - accept = file.isDirectory() && (file.listFiles(ignoreFile).length == 0); - - return accept; - } - - /** - * Ignores directory (and children) if it contains a file named ".jbakeignore". - * - * @param file the file to test - * @return true if file is directory and not ignored - * @deprecated use {@link #directoryOnlyIfNotIgnored(File, JBakeConfiguration)} instead - */ - @Deprecated - public static boolean directoryOnlyIfNotIgnored(File file) { - boolean accept = false; - - FilenameFilter ignoreFile = new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.equalsIgnoreCase(".jbakeignore"); - } - }; - - accept = file.isDirectory() && (file.listFiles(ignoreFile).length == 0); - - return accept; - } - - public static boolean isExistingFolder(File f) { - return null != f && f.exists() && f.isDirectory(); - } - - /** - * Works out the folder where JBake is running from. - * - * @return File referencing folder JBake is running from - * @throws Exception when application is not able to work out where is JBake running from - */ - public static File getRunningLocation() throws Exception { - String codePath = FileUtil.class.getProtectionDomain().getCodeSource().getLocation().getPath(); - String decodedPath = URLDecoder.decode(codePath, "UTF-8"); - File codeFile = new File(decodedPath); - if (!codeFile.exists()) { - throw new Exception("Cannot locate running location of JBake!"); - } - File codeFolder = codeFile.getParentFile().getParentFile(); - if (!codeFolder.exists()) { - throw new Exception("Cannot locate running location of JBake!"); - } - - return codeFolder; - } - - public static String fileExt(File src) { - String name = src.getName(); - return fileExt(name); - } - - public static String fileExt(String name) { - int idx = name.lastIndexOf('.'); - if (idx > 0) { - return name.substring(idx + 1); - } else { - return ""; - } - } - - /** - * Computes the hash of a file or directory. - * - * @param sourceFile the original file or directory - * @return an hex string representing the SHA1 hash of the file or directory. - * @throws Exception if any IOException of SecurityException occured - */ - public static String sha1(File sourceFile) throws Exception { - byte[] buffer = new byte[1024]; - MessageDigest complete = MessageDigest.getInstance("SHA-1"); - updateDigest(complete, sourceFile, buffer); - byte[] bytes = complete.digest(); - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } - - private static void updateDigest(final MessageDigest digest, final File sourceFile, final byte[] buffer) throws IOException { - if (sourceFile.isFile()) { - try (InputStream fis = new FileInputStream(sourceFile)) { - int numRead; - do { - numRead = fis.read(buffer); - if (numRead > 0) { - digest.update(buffer, 0, numRead); - } - } while (numRead != -1); - } - } else if (sourceFile.isDirectory()) { - File[] files = sourceFile.listFiles(); - if (files != null) { - for (File file : files) { - updateDigest(digest, file, buffer); - } - } - } - } - - /** - * platform independent file.getPath() - * - * @param file the file to transform, or {@code null} - * @return The result of file.getPath() with all path Separators beeing a "/", or {@code null} - * Needed to transform Windows path separators into slashes. - */ - public static String asPath(File file) { - if (file == null) { - return null; - } - return asPath(file.getPath()); - } - - /** - * platform independent file.getPath() - * - * @param path the path to transform, or {@code null} - * @return The result will have all platform path separators replaced by "/". - */ - public static String asPath(String path) { - if (path == null) { - return null; - } - - // On windows we have to replace the backslash - if (!File.separator.equals(FileUtil.URI_SEPARATOR_CHAR)) { - return path.replace(File.separator, FileUtil.URI_SEPARATOR_CHAR); - } else { - return path; - } - } - - /** - * Given a file inside content it return - * the relative path to get to the root. - *

- * Example: /content and /content/tags/blog will return '../..' - * - * @param sourceFile the file to calculate relative path for - * @param rootPath the root path - * @param config the jbake configuration - * @return the relative path to get to the root - */ - static public String getPathToRoot(JBakeConfiguration config, File rootPath, File sourceFile) { - - Path r = Paths.get(rootPath.toURI()); - Path s = Paths.get(sourceFile.getParentFile().toURI()); - Path relativePath = s.relativize(r); - - StringBuilder sb = new StringBuilder(); - - sb.append(asPath(relativePath.toString())); - - if (config.getUriWithoutExtension()) { - sb.append("/.."); - } - if (sb.length() > 0) { // added as calling logic assumes / at end. - sb.append("/"); - } - return sb.toString(); - } - - static public String getUriPathToDestinationRoot(JBakeConfiguration config, File sourceFile) { - return getPathToRoot(config, config.getDestinationFolder(), sourceFile); - } - - static public String getUriPathToContentRoot(JBakeConfiguration config, File sourceFile) { - return getPathToRoot(config, config.getContentFolder(), sourceFile); - } - - /** - * Utility method to determine if a given file is located somewhere in the directory provided. - * - * @param file used to check if it is located in the provided directory. - * @param directory to validate whether or not the provided file resides. - * @return true if the file is somewhere in the provided directory, false if it is not. - * @throws IOException if the canonical path for either of the input directories can't be determined. - */ - public static boolean isFileInDirectory(File file, File directory) throws IOException { - return (file.exists() - && !file.isHidden() - && directory.isDirectory() - && file.getCanonicalPath().startsWith(directory.getCanonicalPath())); - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/JBakeException.java b/jbake-core/src/main/java/org/jbake/app/JBakeException.java deleted file mode 100644 index f3e858a4a..000000000 --- a/jbake-core/src/main/java/org/jbake/app/JBakeException.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jbake.app; - -import org.jbake.launcher.SystemExit; - -/** - * This runtime exception is thrown by JBake API to indicate an processing - * error. - *

- * It always contains an error message and if available the cause. - */ -public class JBakeException extends RuntimeException { - private static final long serialVersionUID = 1L; - - final private SystemExit exit; - - /** - * - * @param message - * The error message. - * @param cause - * The causing exception or null if no cause - * available. - */ - public JBakeException(final SystemExit exit, final String message, final Throwable cause) { - super(message, cause); - this.exit = exit; - } - - public JBakeException(final SystemExit exit, final String message) { - this(exit, message, null); - } - - public int getExit() { - return exit.getStatus(); - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/Oven.java b/jbake-core/src/main/java/org/jbake/app/Oven.java deleted file mode 100644 index ead7ce2cf..000000000 --- a/jbake-core/src/main/java/org/jbake/app/Oven.java +++ /dev/null @@ -1,240 +0,0 @@ -package org.jbake.app; - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.apache.commons.lang3.LocaleUtils; -import org.jbake.app.configuration.DefaultJBakeConfiguration; -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.app.configuration.JBakeConfigurationFactory; -import org.jbake.app.configuration.JBakeConfigurationInspector; -import org.jbake.model.DocumentTypes; -import org.jbake.render.RenderingTool; -import org.jbake.template.ModelExtractors; -import org.jbake.template.ModelExtractorsDocumentTypeListener; -import org.jbake.template.RenderingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.ServiceLoader; - -/** - * All the baking happens in the Oven! - * - * @author Jonathan Bullock jonbullock@gmail.com - */ -public class Oven { - - private static final Logger LOGGER = LoggerFactory.getLogger(Oven.class); - - private final Utensils utensils; - private final List errors = new LinkedList<>(); - private int renderedCount = 0; - - /** - * @param source Project source directory - * @param destination The destination folder - * @param isClearCache Should the cache be cleaned - * @throws Exception if configuration is not loaded correctly - * @deprecated Use {@link #Oven(JBakeConfiguration)} instead - * Delegate c'tor to prevent API break for the moment. - */ - @Deprecated - public Oven(final File source, final File destination, final boolean isClearCache) throws Exception { - this(new JBakeConfigurationFactory().createDefaultJbakeConfiguration(source, destination, isClearCache)); - } - - /** - * @param source Project source directory - * @param destination The destination folder - * @param config Project configuration - * @param isClearCache Should the cache be cleaned - * @throws Exception if configuration is not loaded correctly - * @deprecated Use {@link #Oven(JBakeConfiguration)} instead - * Creates a new instance of the Oven with references to the source and destination folders. - */ - @Deprecated - public Oven(final File source, final File destination, final CompositeConfiguration config, final boolean isClearCache) throws Exception { - this(new JBakeConfigurationFactory().createDefaultJbakeConfiguration(source, destination, config, isClearCache)); - } - - /** - * Create an Oven instance by a {@link JBakeConfiguration} - *

- * It creates default {@link Utensils} needed to bake sites. - * - * @param config The project configuration. see {@link JBakeConfiguration} - */ - public Oven(JBakeConfiguration config) { - this.utensils = UtensilsFactory.createDefaultUtensils(config); - } - - /** - * Create an Oven instance with given {@link Utensils} - * - * @param utensils All Utensils necessary to bake - */ - public Oven(Utensils utensils) { - checkConfiguration(utensils.getConfiguration()); - this.utensils = utensils; - } - - @Deprecated - public CompositeConfiguration getConfig() { - return ((DefaultJBakeConfiguration) utensils.getConfiguration()).getCompositeConfiguration(); - } - - // TODO: do we want to use this. Else, config could be final - @Deprecated - public void setConfig(final CompositeConfiguration config) { - ((DefaultJBakeConfiguration) utensils.getConfiguration()).setCompositeConfiguration(config); - } - - /** - * Checks source path contains required sub-folders (i.e. templates) and setups up variables for them. - * - * @deprecated There is no need for this method anymore. Validation is now part of the instantiation. - * Can be removed with 3.0.0. - */ - @Deprecated - public void setupPaths() { - /* nothing to do here */ - } - - /** - * Checks source path contains required sub-folders (i.e. templates) and setups up variables for them. - * Creates destination folder if it does not exist. - * - * @throws JBakeException If template or contents folder don't exist - */ - private void checkConfiguration(JBakeConfiguration configuration) { - JBakeConfigurationInspector inspector = new JBakeConfigurationInspector(configuration); - inspector.inspect(); - } - - /** - * Sets the Locale for the JVM - * - */ - private void setLocale() { - String localeString = getUtensils().getConfiguration().getJvmLocale(); - Locale locale = localeString != null ? LocaleUtils.toLocale(localeString) : Locale.getDefault(); - Locale.setDefault(locale); - } - - /** - * Responsible for incremental baking, typically a single file at a time. - * - * @param fileToBake The file to bake - */ - public void bake(File fileToBake) { - Asset asset = utensils.getAsset(); - if(asset.isAssetFile(fileToBake)) { - LOGGER.info("Baking a change to an asset [" + fileToBake.getPath() + "]"); - asset.copySingleFile(fileToBake); - } else { - LOGGER.info("Playing it safe and running a full bake..."); - bake(); - } - } - - /** - * All the good stuff happens in here... - */ - public void bake() { - - ContentStore contentStore = utensils.getContentStore(); - JBakeConfiguration config = utensils.getConfiguration(); - Crawler crawler = utensils.getCrawler(); - Asset asset = utensils.getAsset(); - setLocale(); - - try { - - final long start = new Date().getTime(); - LOGGER.info("Baking has started..."); - contentStore.startup(); - updateDocTypesFromConfiguration(); - contentStore.updateSchema(); - contentStore.updateAndClearCacheIfNeeded(config.getClearCache(), config.getTemplateFolder()); - - // process source content - crawler.crawl(); - - // process data files - crawler.crawlDataFiles(); - - // render content - renderContent(); - - // copy assets - asset.copy(); - asset.copyAssetsFromContent(config.getContentFolder()); - - errors.addAll(asset.getErrors()); - - LOGGER.info("Baking finished!"); - long end = new Date().getTime(); - LOGGER.info("Baked {} items in {}ms", renderedCount, end - start); - if (!errors.isEmpty()) { - LOGGER.error("Failed to bake {} item(s)!", errors.size()); - } - } finally { - contentStore.close(); - contentStore.shutdown(); - } - } - - /** - * Iterates over the configuration, searching for keys like "template.index.file=..." - * in order to register new document types. - */ - private void updateDocTypesFromConfiguration() { - resetDocumentTypesAndExtractors(); - JBakeConfiguration config = utensils.getConfiguration(); - - ModelExtractorsDocumentTypeListener listener = new ModelExtractorsDocumentTypeListener(); - DocumentTypes.addListener(listener); - - for (String docType : config.getDocumentTypes()) { - DocumentTypes.addDocumentType(docType); - } - - // needs manually setting as this isn't defined in same way as document types for content files - DocumentTypes.addDocumentType(config.getDataFileDocType()); - } - - private void resetDocumentTypesAndExtractors() { - DocumentTypes.resetDocumentTypes(); - ModelExtractors.getInstance().reset(); - } - - /** - * Load {@link RenderingTool} instances and delegate rendering of documents to them - */ - private void renderContent() { - JBakeConfiguration config = utensils.getConfiguration(); - Renderer renderer = utensils.getRenderer(); - ContentStore contentStore = utensils.getContentStore(); - - for (RenderingTool tool : ServiceLoader.load(RenderingTool.class)) { - try { - renderedCount += tool.render(renderer, contentStore, config); - } catch (RenderingException e) { - errors.add(e); - } - } - } - - public List getErrors() { - return new ArrayList<>(errors); - } - - public Utensils getUtensils() { - return utensils; - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/Parser.java b/jbake-core/src/main/java/org/jbake/app/Parser.java deleted file mode 100644 index 129b01abf..000000000 --- a/jbake-core/src/main/java/org/jbake/app/Parser.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.jbake.app; - -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.model.DocumentModel; -import org.jbake.parser.Engines; -import org.jbake.parser.ParserEngine; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - -/** - * Parses a File for content. - * - * @author Jonathan Bullock jonbullock@gmail.com - */ -public class Parser { - private static final Logger LOGGER = LoggerFactory.getLogger(Parser.class); - - private JBakeConfiguration config; - - /** - * Creates a new instance of Parser. - * - * @param config Project configuration - */ - public Parser(JBakeConfiguration config) { - this.config = config; - } - - /** - * Process the file by parsing the contents. - * - * @param file File input for parsing - * @return The contents of the file - */ - public DocumentModel processFile(File file) { - ParserEngine engine = Engines.get(FileUtil.fileExt(file)); - if (engine == null) { - LOGGER.error("Unable to find suitable markup engine for {}", file); - return null; - } - - return engine.parse(config, file); - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/Renderer.java b/jbake-core/src/main/java/org/jbake/app/Renderer.java deleted file mode 100644 index 5508fd327..000000000 --- a/jbake-core/src/main/java/org/jbake/app/Renderer.java +++ /dev/null @@ -1,448 +0,0 @@ -package org.jbake.app; - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.jbake.app.configuration.DefaultJBakeConfiguration; -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.app.configuration.JBakeConfigurationFactory; -import org.jbake.model.DocumentModel; -import org.jbake.model.ModelAttributes; -import org.jbake.template.DelegatingTemplateEngine; -import org.jbake.template.model.TemplateModel; -import org.jbake.util.PagingHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.file.Files; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; - -/** - * Render output to a file. - * - * @author Jonathan Bullock jonbullock@gmail.com - */ -public class Renderer { - private static final String MASTERINDEX_TEMPLATE_NAME = "masterindex"; - private static final String SITEMAP_TEMPLATE_NAME = "sitemap"; - private static final String FEED_TEMPLATE_NAME = "feed"; - private static final String ARCHIVE_TEMPLATE_NAME = "archive"; - private static final String ERROR404_TEMPLATE_NAME = "error404"; - - private final Logger logger = LoggerFactory.getLogger(Renderer.class); - private final JBakeConfiguration config; - private final DelegatingTemplateEngine renderingEngine; - private final ContentStore db; - - /** - * @param db The database holding the content - * @param destination The destination folder - * @param templatesPath The templates folder - * @param config Project configuration - * @deprecated Use {@link #Renderer(ContentStore, JBakeConfiguration)} instead. - * Creates a new instance of Renderer with supplied references to folders. - */ - @Deprecated - public Renderer(ContentStore db, File destination, File templatesPath, CompositeConfiguration config) { - this(db, new JBakeConfigurationFactory().createDefaultJbakeConfiguration(templatesPath.getParentFile(), config)); - DefaultJBakeConfiguration configuration = ((DefaultJBakeConfiguration) this.config); - configuration.setDestinationFolder(destination); - configuration.setTemplateFolder(templatesPath); - } - - // TOqDO: should all content be made available to all templates via this class?? - - /** - * @param db The database holding the content - * @param destination The destination folder - * @param templatesPath The templates folder - * @param config Project configuration - * @param renderingEngine The instance of DelegatingTemplateEngine to use - * @deprecated Use {@link #Renderer(ContentStore, JBakeConfiguration, DelegatingTemplateEngine)} instead. - * Creates a new instance of Renderer with supplied references to folders and the instance of DelegatingTemplateEngine to use. - */ - @Deprecated - public Renderer(ContentStore db, File destination, File templatesPath, CompositeConfiguration config, DelegatingTemplateEngine renderingEngine) { - this(db, new JBakeConfigurationFactory().createDefaultJbakeConfiguration(templatesPath.getParentFile(), config), renderingEngine); - DefaultJBakeConfiguration configuration = ((DefaultJBakeConfiguration) this.config); - configuration.setDestinationFolder(destination); - configuration.setTemplateFolder(templatesPath); - } - - /** - * Creates a new instance of Renderer with supplied references to folders. - * - * @param db The database holding the content - * @param config Project configuration - */ - public Renderer(ContentStore db, JBakeConfiguration config) { - this.config = config; - this.renderingEngine = new DelegatingTemplateEngine(db, config); - this.db = db; - } - - /** - * Creates a new instance of Renderer with supplied references to folders and the instance of DelegatingTemplateEngine to use. - * - * @param db The database holding the content - * @param config The application specific configuration - * @param renderingEngine The instance of DelegatingTemplateEngine to use - */ - public Renderer(ContentStore db, JBakeConfiguration config, DelegatingTemplateEngine renderingEngine) { - this.config = config; - this.renderingEngine = renderingEngine; - this.db = db; - } - - private String findTemplateName(String docType) { - return config.getTemplateByDocType(docType); - } - - /** - * Render the supplied content to a file. - * - * @param content The content to renderDocument - * @throws Exception if IOException or SecurityException are raised - */ - public void render(DocumentModel content) throws Exception { - String docType = content.getType(); - String outputFilename = config.getDestinationFolder().getPath() + File.separatorChar + content.getUri(); - if (outputFilename.lastIndexOf('.') > outputFilename.lastIndexOf(File.separatorChar)) { - outputFilename = outputFilename.substring(0, outputFilename.lastIndexOf('.')); - } - - // delete existing versions if they exist in case status has changed either way - String outputExtension = config.getOutputExtensionByDocType(docType); - File draftFile = new File(outputFilename, config.getDraftSuffix() + outputExtension); - if (draftFile.exists()) { - Files.delete(draftFile.toPath()); - } - - File publishedFile = new File(outputFilename + outputExtension); - if (publishedFile.exists()) { - Files.delete(publishedFile.toPath()); - } - - if (content.getStatus().equals(ModelAttributes.Status.DRAFT)) { - outputFilename = outputFilename + config.getDraftSuffix(); - } - - File outputFile = new File(outputFilename + outputExtension); - TemplateModel model = new TemplateModel(); - model.setContent(content); - model.setRenderer(renderingEngine); - - try { - try (Writer out = createWriter(outputFile)) { - renderingEngine.renderDocument(model, findTemplateName(docType), out); - } - logger.info("Rendering [{}]... done!", outputFile); - } catch (Exception e) { - logger.error("Rendering [{}]... failed!", outputFile, e); - throw new Exception("Failed to render file " + outputFile.getAbsolutePath() + ". Cause: " + e.getMessage(), e); - } - } - - private Writer createWriter(File file) throws IOException { - if (!file.exists()) { - file.getParentFile().mkdirs(); - file.createNewFile(); - } - - return new OutputStreamWriter(new FileOutputStream(file), config.getRenderEncoding()); - } - - private void render(RenderingConfig renderConfig) throws Exception { - File outputFile = renderConfig.getPath(); - try { - try (Writer out = createWriter(outputFile)) { - renderingEngine.renderDocument(renderConfig.getModel(), renderConfig.getTemplate(), out); - } - logger.info("Rendering {} [{}]... done!", renderConfig.getName(), outputFile); - } catch (Exception e) { - logger.error("Rendering {} [{}]... failed!", renderConfig.getName(), outputFile, e); - throw new Exception("Failed to render " + renderConfig.getName(), e); - } - } - - /** - * Render an index file using the supplied content. - * - * @param indexFile The name of the output file - * @throws Exception if IOException or SecurityException are raised - */ - public void renderIndex(String indexFile) throws Exception { - render(new DefaultRenderingConfig(indexFile, MASTERINDEX_TEMPLATE_NAME)); - } - - public void renderIndexPaging(String indexFile) throws Exception { - long totalPosts = db.getPublishedCount("post"); - int postsPerPage = config.getPostsPerPage(); - - if (totalPosts == 0) { - //paging makes no sense. render single index file instead - renderIndex(indexFile); - } else { - PagingHelper pagingHelper = new PagingHelper(totalPosts, postsPerPage); - - TemplateModel model = new TemplateModel(); - model.setRenderer(renderingEngine); - model.setNumberOfPages(pagingHelper.getNumberOfPages()); - - try { - db.setLimit(postsPerPage); - for (int pageStart = 0, page = 1; pageStart < totalPosts; pageStart += postsPerPage, page++) { - String fileName = indexFile; - - db.setStart(pageStart); - model.setCurrentPageNuber(page); - String previous = pagingHelper.getPreviousFileName(page); - model.setPreviousFilename(previous); - String nextFileName = pagingHelper.getNextFileName(page); - model.setNextFileName(nextFileName); - - DocumentModel contentModel = buildSimpleModel(MASTERINDEX_TEMPLATE_NAME); - - if (page > 1) { - contentModel.setRootPath("../"); - } - model.setContent(contentModel); - - // Add page number to file name - fileName = pagingHelper.getCurrentFileName(page, fileName); - ModelRenderingConfig renderConfig = new ModelRenderingConfig(fileName, model, MASTERINDEX_TEMPLATE_NAME); - render(renderConfig); - } - db.resetPagination(); - } catch (Exception e) { - throw new Exception("Failed to render index. Cause: " + e.getMessage(), e); - } - } - } - - /** - * Render an XML sitemap file using the supplied content. - * - * @param sitemapFile configuration for site map - * @throws Exception if can't create correct default rendering config - * @see About Sitemaps - * @see Sitemap protocol - */ - public void renderSitemap(String sitemapFile) throws Exception { - render(new DefaultRenderingConfig(sitemapFile, SITEMAP_TEMPLATE_NAME)); - } - - /** - * Render an XML feed file using the supplied content. - * - * @param feedFile The name of the output file - * @throws Exception if default rendering configuration is not loaded correctly - */ - public void renderFeed(String feedFile) throws Exception { - render(new DefaultRenderingConfig(feedFile, FEED_TEMPLATE_NAME)); - } - - /** - * Render an archive file using the supplied content. - * - * @param archiveFile The name of the output file - * @throws Exception if default rendering configuration is not loaded correctly - */ - public void renderArchive(String archiveFile) throws Exception { - render(new DefaultRenderingConfig(archiveFile, ARCHIVE_TEMPLATE_NAME)); - } - - /** - * Render an 404 file using the predefined template. - * - * @param errorFile The name of the output file - * @throws Exception if default rendering configuration is not loaded correctly - */ - public void renderError404(String errorFile) throws Exception { - render(new DefaultRenderingConfig(errorFile, ERROR404_TEMPLATE_NAME)); - } - - /** - * Render tag files using the supplied content. - * - * @param tagPath The output path - * @return Number of rendered tags - * @throws Exception if cannot render tags correctly - */ - public int renderTags(String tagPath) throws Exception { - int renderedCount = 0; - final List errors = new LinkedList<>(); - - for (String tag : db.getAllTags()) { - try { - TemplateModel model = new TemplateModel(); - model.setRenderer(renderingEngine); - model.setTag(tag); - DocumentModel map = buildSimpleModel(ModelAttributes.TAG.toString()); - File path = new File(config.getDestinationFolder() + File.separator + tagPath + File.separator + tag + config.getOutputExtension()); - - map.setRootPath(FileUtil.getUriPathToDestinationRoot(config, path)); - model.setContent(map); - - render(new ModelRenderingConfig(path, ModelAttributes.TAG.toString(), model, findTemplateName(ModelAttributes.TAG.toString()))); - - renderedCount++; - } catch (Exception e) { - errors.add(e); - } - } - - if (config.getRenderTagsIndex()) { - try { - // Add an index file at root folder of tags. - // This will prevent directory listing and also provide an option to - // display all tags page. - TemplateModel model = new TemplateModel(); - model.setRenderer(renderingEngine); - DocumentModel map = buildSimpleModel(ModelAttributes.TAGS.toString()); - File path = new File(config.getDestinationFolder() + File.separator + tagPath + File.separator + "index" + config.getOutputExtension()); - - map.setRootPath(FileUtil.getUriPathToDestinationRoot(config, path)); - model.setContent(map); - - - render(new ModelRenderingConfig(path, "tagindex", model, findTemplateName("tagsindex"))); - renderedCount++; - } catch (Exception e) { - errors.add(e); - } - } - - if (!errors.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("Failed to render tags. Cause(s):"); - for (Throwable error : errors) { - sb.append("\n").append(error.getMessage()); - } - throw new Exception(sb.toString(), errors.get(0)); - } else { - return renderedCount; - } - } - - /** - * Builds simple map of values, which are exposed when rendering index/archive/sitemap/feed/tags. - * - * @param type - * @return a basic {@link DocumentModel} - */ - private DocumentModel buildSimpleModel(String type) { - DocumentModel content = new DocumentModel(); - content.setType(type); - content.setRootPath(""); - // add any more keys here that need to have a default value to prevent need to perform null check in templates - return content; - } - - private interface RenderingConfig { - - File getPath(); - - String getName(); - - String getTemplate(); - - TemplateModel getModel(); - } - - private abstract static class AbstractRenderingConfig implements RenderingConfig { - - protected final File path; - protected final String name; - protected final String template; - - public AbstractRenderingConfig(File path, String name, String template) { - super(); - this.path = path; - this.name = name; - this.template = template; - } - - @Override - public File getPath() { - return path; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getTemplate() { - return template; - } - - } - - public class ModelRenderingConfig extends AbstractRenderingConfig { - private final TemplateModel model; - - public ModelRenderingConfig(String fileName, TemplateModel model, String templateType) { - super(new File(config.getDestinationFolder(), fileName), fileName, findTemplateName(templateType)); - this.model = model; - } - - public ModelRenderingConfig(File path, String name, TemplateModel model, String template) { - super(path, name, template); - this.model = model; - } - - @Override - public TemplateModel getModel() { - return model; - } - } - - class DefaultRenderingConfig extends AbstractRenderingConfig { - - private final DocumentModel content; - - private DefaultRenderingConfig(File path, String allInOneName) { - super(path, allInOneName, findTemplateName(allInOneName)); - this.content = buildSimpleModel(allInOneName); - } - - public DefaultRenderingConfig(String filename, String allInOneName) { - super(new File(config.getDestinationFolder(), File.separator + filename), allInOneName, findTemplateName(allInOneName)); - this.content = buildSimpleModel(allInOneName); - } - - /** - * Constructor added due to known use of a allInOneName which is used for name, template and content - * - * @param allInOneName - */ - public DefaultRenderingConfig(String allInOneName) { - this(new File(config.getDestinationFolder().getPath() + File.separator + allInOneName + config.getOutputExtension()), - allInOneName); - } - - @Override - public TemplateModel getModel() { - TemplateModel model = new TemplateModel(); - model.setRenderer(renderingEngine); - model.setContent(content); - - if (config.getPaginateIndex()) { - model.setNumberOfPages(0); - model.setCurrentPageNuber(0); - model.setPreviousFilename(""); - model.setNextFileName(""); - } - - return model; - } - - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/Utensils.java b/jbake-core/src/main/java/org/jbake/app/Utensils.java deleted file mode 100644 index 648278cfe..000000000 --- a/jbake-core/src/main/java/org/jbake/app/Utensils.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.jbake.app; - -import org.jbake.app.configuration.JBakeConfiguration; - -/** - * A helper class to wrap all the utensils that are needed to bake. - */ -public class Utensils { - private JBakeConfiguration configuration; - private ContentStore contentStore; - private Crawler crawler; - private Renderer renderer; - private Asset asset; - - public JBakeConfiguration getConfiguration() { - return configuration; - } - - public void setConfiguration(JBakeConfiguration configuration) { - this.configuration = configuration; - } - - public ContentStore getContentStore() { - return contentStore; - } - - public void setContentStore(ContentStore contentStore) { - this.contentStore = contentStore; - } - - public Crawler getCrawler() { - return crawler; - } - - public void setCrawler(Crawler crawler) { - this.crawler = crawler; - } - - public Renderer getRenderer() { - return renderer; - } - - public void setRenderer(Renderer renderer) { - this.renderer = renderer; - } - - public Asset getAsset() { - return asset; - } - - public void setAsset(Asset asset) { - this.asset = asset; - } -} - diff --git a/jbake-core/src/main/java/org/jbake/app/UtensilsFactory.java b/jbake-core/src/main/java/org/jbake/app/UtensilsFactory.java deleted file mode 100644 index 5e2038193..000000000 --- a/jbake-core/src/main/java/org/jbake/app/UtensilsFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jbake.app; - -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.app.configuration.JBakeConfigurationInspector; - -/** - * A factory to create a {@link Utensils} object - */ -public class UtensilsFactory { - - /** - * Create default {@link Utensils} by a given {@link JBakeConfiguration} - * @param config a {@link JBakeConfiguration} - * @return a default {@link Utensils} instance - */ - public static Utensils createDefaultUtensils(JBakeConfiguration config) { - - JBakeConfigurationInspector inspector = new JBakeConfigurationInspector(config); - inspector.inspect(); - - Utensils utensils = new Utensils(); - utensils.setConfiguration(config); - ContentStore contentStore = DBUtil.createDataStore(config); - utensils.setContentStore(contentStore); - utensils.setCrawler(new Crawler(contentStore, config)); - utensils.setRenderer(new Renderer(contentStore, config)); - utensils.setAsset(new Asset(config)); - - return utensils; - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/ZipUtil.java b/jbake-core/src/main/java/org/jbake/app/ZipUtil.java deleted file mode 100644 index 481b94a10..000000000 --- a/jbake-core/src/main/java/org/jbake/app/ZipUtil.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.jbake.app; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -/** - * Provides Zip file related functions - * - * @author Jonathan Bullock jonbullock@gmail.com - * - */ -public class ZipUtil { - - /** - * Extracts content of Zip file to specified output path. - * - * @param is {@link InputStream} InputStream of Zip file - * @param outputFolder folder where Zip file should be extracted to - * @throws IOException if IOException occurs - */ - public static void extract(InputStream is, File outputFolder) throws IOException { - ZipInputStream zis = new ZipInputStream(is); - ZipEntry entry; - byte[] buffer = new byte[1024]; - - while ((entry = zis.getNextEntry()) != null) { - File outputFile = new File(outputFolder.getCanonicalPath() + File.separatorChar + entry.getName()); - File outputParent = new File(outputFile.getParent()); - outputParent.mkdirs(); - - if (entry.isDirectory()) { - if (!outputFile.exists()) { - outputFile.mkdir(); - } - } else { - try (FileOutputStream fos = new FileOutputStream(outputFile)) { - int len; - while ((len = zis.read(buffer)) > 0) { - fos.write(buffer, 0, len); - } - } - } - } - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/ConfigUtil.java b/jbake-core/src/main/java/org/jbake/app/configuration/ConfigUtil.java deleted file mode 100644 index c0c6260f4..000000000 --- a/jbake-core/src/main/java/org/jbake/app/configuration/ConfigUtil.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.jbake.app.configuration; - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.apache.commons.configuration2.PropertiesConfiguration; -import org.apache.commons.configuration2.SystemConfiguration; -import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; -import org.apache.commons.configuration2.builder.fluent.Parameters; -import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler; -import org.apache.commons.configuration2.ex.ConfigurationException; -import org.jbake.app.JBakeException; -import org.jbake.launcher.SystemExit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.net.URL; -import java.nio.charset.Charset; - -/** - * Provides Configuration related functions. - * - * @author Jonathan Bullock jonbullock@gmail.com - */ -public class ConfigUtil { - - public static final char LIST_DELIMITER = ','; - public static final String DEFAULT_ENCODING = "UTF-8"; - private static final Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class); - public static final String LEGACY_CONFIG_FILE = "custom.properties"; - public static final String CONFIG_FILE = "jbake.properties"; - public static final String DEFAULT_CONFIG_FILE = "default.properties"; - private String encoding = DEFAULT_ENCODING; - - private CompositeConfiguration load(File source, File propertiesFile) throws ConfigurationException { - - if (!source.exists()) { - throw new JBakeException(SystemExit.CONFIGURATION_ERROR, "The given source folder '" + source.getAbsolutePath() + "' does not exist."); - } - if (!source.isDirectory()) { - throw new JBakeException(SystemExit.CONFIGURATION_ERROR,"The given source folder is not a directory."); - } - - File legacyConfigFile = new File(source, LEGACY_CONFIG_FILE); - File customConfigFile = propertiesFile != null ? propertiesFile : new File(source, CONFIG_FILE); - - CompositeConfiguration config = new CompositeConfiguration(); - config.setListDelimiterHandler(new DefaultListDelimiterHandler(LIST_DELIMITER)); - - if (legacyConfigFile.exists()) { - displayLegacyConfigFileWarningIfRequired(); - config.addConfiguration(getFileBasedPropertiesConfiguration(legacyConfigFile)); - } - if (customConfigFile.exists()) { - config.addConfiguration(getFileBasedPropertiesConfiguration(customConfigFile)); - } - URL defaultPropertiesLocation = this.getClass().getClassLoader().getResource(DEFAULT_CONFIG_FILE); - if (defaultPropertiesLocation != null) { - config.addConfiguration(getFileBasedPropertiesConfiguration(defaultPropertiesLocation)); - } - - config.addConfiguration(new SystemConfiguration()); - return config; - } - - private PropertiesConfiguration getFileBasedPropertiesConfiguration(File propertiesFile) throws ConfigurationException { - FileBasedConfigurationBuilder builder = - new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class) - .configure(new Parameters().properties() - .setFile(propertiesFile) - .setEncoding(encoding) - .setThrowExceptionOnMissing(true) - .setListDelimiterHandler(new DefaultListDelimiterHandler(LIST_DELIMITER)) - .setIncludesAllowed(false)); - return builder.getConfiguration(); - } - - private PropertiesConfiguration getFileBasedPropertiesConfiguration(URL propertiesFile) throws ConfigurationException { - FileBasedConfigurationBuilder builder = - new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class) - .configure(new Parameters().properties() - .setURL(propertiesFile) - .setEncoding(encoding) - .setThrowExceptionOnMissing(true) - .setListDelimiterHandler(new DefaultListDelimiterHandler(LIST_DELIMITER)) - .setIncludesAllowed(false)); - return builder.getConfiguration(); - } - - private void displayLegacyConfigFileWarningIfRequired() { - LOGGER.warn("You have defined a part of your JBake configuration in {}", LEGACY_CONFIG_FILE); - LOGGER.warn("Usage of this file is being deprecated, please rename this file to: {} to remove this warning", CONFIG_FILE); - } - - /** - * Load a configuration. - * - * @param source the source directory of the project - * @param propertiesFile the properties file for the project - * @return the configuration - * @throws JBakeException if unable to configure - */ - public JBakeConfiguration loadConfig(File source, File propertiesFile) throws JBakeException { - try { - CompositeConfiguration configuration = load(source, propertiesFile); - return new DefaultJBakeConfiguration(source, configuration); - } catch (ConfigurationException e) { - throw new JBakeException(SystemExit.CONFIGURATION_ERROR, e.getMessage(), e); - } - } - - /** - * Load a configuration. - * - * @param source the source directory of the project - * @return the configuration - * @throws ConfigurationException if unable to configure - * @deprecated use {@link #loadConfig(File, File)} instead - */ - @Deprecated - public JBakeConfiguration loadConfig(File source) throws ConfigurationException { - return loadConfig(source, null); - } - - public String getEncoding() { - return this.encoding; - } - - public ConfigUtil setEncoding(String encoding) { - if (Charset.isSupported(encoding)) { - this.encoding = encoding; - } else { - this.encoding = DEFAULT_ENCODING; - LOGGER.warn("Unsupported encoding '{}'. Using default encoding '{}'", encoding, this.encoding); - } - return this; - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java b/jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java deleted file mode 100644 index 223b0f624..000000000 --- a/jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java +++ /dev/null @@ -1,714 +0,0 @@ -package org.jbake.app.configuration; - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.apache.commons.configuration2.Configuration; -import org.apache.commons.configuration2.MapConfiguration; -import org.apache.commons.configuration2.SystemConfiguration; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.jbake.app.configuration.PropertyList.*; - -/** - * The default implementation of a {@link JBakeConfiguration} - */ -public class DefaultJBakeConfiguration implements JBakeConfiguration { - - - public static final String DEFAULT_TYHMELEAF_TEMPLATE_MODE = "HTML"; - private static final String SOURCE_FOLDER_KEY = "sourceFolder"; - private static final String DESTINATION_FOLDER_KEY = "destinationFolder"; - private static final String ASSET_FOLDER_KEY = "assetFolder"; - private static final String TEMPLATE_FOLDER_KEY = "templateFolder"; - private static final String CONTENT_FOLDER_KEY = "contentFolder"; - private static final String DATA_FOLDER_KEY = "dataFolder"; - private static final Pattern TEMPLATE_DOC_PATTERN = Pattern.compile("(?:template\\.)([a-zA-Z0-9-_]+)(?:\\.file)"); - private static final String DOCTYPE_FILE_POSTFIX = ".file"; - private static final String DOCTYPE_EXTENSION_POSTFIX = ".extension"; - private static final String DOCTYPE_TEMPLATE_PREFIX = "template."; - private final Logger logger = LoggerFactory.getLogger(DefaultJBakeConfiguration.class); - private CompositeConfiguration compositeConfiguration; - - /** - * Some deprecated implementations just need access to the configuration without access to the source folder - * - * @param configuration The project configuration - * @deprecated use {@link #DefaultJBakeConfiguration(File, CompositeConfiguration)} instead - */ - @Deprecated - public DefaultJBakeConfiguration(CompositeConfiguration configuration) { - this.compositeConfiguration = configuration; - } - - public DefaultJBakeConfiguration(File sourceFolder, CompositeConfiguration configuration) { - this.compositeConfiguration = configuration; - setSourceFolder(sourceFolder); - setupPaths(); - } - - @Override - public Object get(String key) { - return compositeConfiguration.getProperty(key); - } - - @Override - public String getArchiveFileName() { - return getAsString(ARCHIVE_FILE.getKey()); - } - - private boolean getAsBoolean(String key) { - return compositeConfiguration.getBoolean(key, false); - } - - private File getAsFolder(String key) { - return (File) get(key); - } - - private int getAsInt(String key, int defaultValue) { - return compositeConfiguration.getInt(key, defaultValue); - } - - private List getAsList(String key) { - return compositeConfiguration.getList(String.class, key); - } - - private String getAsString(String key) { - return compositeConfiguration.getString(key); - } - - private String getAsString(String key, String defaultValue) { - return compositeConfiguration.getString(key, defaultValue); - } - - @Override - public List getAsciidoctorAttributes() { - return getAsList(ASCIIDOCTOR_ATTRIBUTES.getKey()); - } - - public List getAsciidoctorOption(String optionKey) { - Configuration subConfig = compositeConfiguration.subset(ASCIIDOCTOR_OPTION.getKey()); - - if (subConfig.containsKey(optionKey)) { - return subConfig.getList(String.class, optionKey); - } else { - logger.warn("Cannot find asciidoctor option '{}.{}'", ASCIIDOCTOR_OPTION.getKey(), optionKey); - return Collections.emptyList(); - } - } - - @Override - public List getAsciidoctorOptionKeys() { - List options = new ArrayList<>(); - Configuration subConfig = compositeConfiguration.subset(ASCIIDOCTOR_OPTION.getKey()); - - Iterator iterator = subConfig.getKeys(); - while (iterator.hasNext()) { - String key = iterator.next(); - options.add(key); - } - - return options; - } - - @Override - public File getAssetFolder() { - return getAsFolder(ASSET_FOLDER_KEY); - } - - public void setAssetFolder(File assetFolder) { - if (assetFolder != null) { - setProperty(ASSET_FOLDER_KEY, assetFolder); - } - } - - @Override - public String getAssetFolderName() { - return getAsString(ASSET_FOLDER.getKey()); - } - - @Override - public boolean getAssetIgnoreHidden() { - return getAsBoolean(ASSET_IGNORE_HIDDEN.getKey()); - } - - public void setAssetIgnoreHidden(boolean assetIgnoreHidden) { - setProperty(ASSET_IGNORE_HIDDEN.getKey(), assetIgnoreHidden); - } - - @Override - public String getAttributesExportPrefixForAsciidoctor() { - return getAsString(ASCIIDOCTOR_ATTRIBUTES_EXPORT_PREFIX.getKey(), ""); - } - - @Override - public String getBuildTimeStamp() { - return getAsString(BUILD_TIMESTAMP.getKey()); - } - - @Override - public boolean getClearCache() { - return getAsBoolean(CLEAR_CACHE.getKey()); - } - - public void setClearCache(boolean clearCache) { - setProperty(CLEAR_CACHE.getKey(), clearCache); - } - - public CompositeConfiguration getCompositeConfiguration() { - return compositeConfiguration; - } - - public void setCompositeConfiguration(CompositeConfiguration configuration) { - this.compositeConfiguration = configuration; - } - - @Override - public File getContentFolder() { - return getAsFolder(CONTENT_FOLDER_KEY); - } - - public void setContentFolder(File contentFolder) { - if (contentFolder != null) { - setProperty(CONTENT_FOLDER_KEY, contentFolder); - } - } - - @Override - public String getContentFolderName() { - return getAsString(CONTENT_FOLDER.getKey()); - } - - @Override - public File getDataFolder() { - return getAsFolder(DATA_FOLDER_KEY); - } - - public void setDataFolder(File dataFolder) { - if (dataFolder != null) { - setProperty(DATA_FOLDER_KEY, dataFolder); - } - } - - @Override - public String getDataFolderName() { - return getAsString(DATA_FOLDER.getKey()); - } - - @Override - public String getDataFileDocType() { - return getAsString(DATA_FILE_DOCTYPE.getKey()); - } - - public void setDataFileDocType(String dataFileDocType) { - setProperty(DATA_FILE_DOCTYPE.getKey(), dataFileDocType); - } - - @Override - public String getDatabasePath() { - return getAsString(DB_PATH.getKey()); - } - - public void setDatabasePath(String path) { - setProperty(DB_PATH.getKey(), path); - } - - @Override - public String getDatabaseStore() { - return getAsString(DB_STORE.getKey()); - } - - public void setDatabaseStore(String storeType) { - setProperty(DB_STORE.getKey(), storeType); - } - - - - @Override - public String getDateFormat() { - return getAsString(DATE_FORMAT.getKey()); - } - - @Override - public String getDefaultStatus() { - return getAsString(DEFAULT_STATUS.getKey(), ""); - } - - public void setDefaultStatus(String status) { - setProperty(DEFAULT_STATUS.getKey(), status); - } - - @Override - public String getDefaultType() { - return getAsString(DEFAULT_TYPE.getKey(), ""); - } - - public void setDefaultType(String type) { - setProperty(DEFAULT_TYPE.getKey(), type); - } - - @Override - public File getDestinationFolder() { - return getAsFolder(DESTINATION_FOLDER_KEY); - } - - public void setDestinationFolder(File destinationFolder) { - if (destinationFolder != null) { - setProperty(DESTINATION_FOLDER_KEY, destinationFolder); - } - } - - @Override - public List getDocumentTypes() { - List docTypes = new ArrayList<>(); - Iterator keyIterator = compositeConfiguration.getKeys(); - while (keyIterator.hasNext()) { - String key = keyIterator.next(); - Matcher matcher = TEMPLATE_DOC_PATTERN.matcher(key); - if (matcher.find()) { - docTypes.add(matcher.group(1)); - } - } - - return docTypes; - } - - @Override - public String getDraftSuffix() { - return getAsString(DRAFT_SUFFIX.getKey(), ""); - } - - @Override - public String getError404FileName() { - return getAsString(ERROR404_FILE.getKey()); - } - - @Override - public String getExampleProjectByType(String templateType) { - return getAsString("example.project." + templateType); - } - - @Override - public boolean getExportAsciidoctorAttributes() { - return getAsBoolean(ASCIIDOCTOR_ATTRIBUTES_EXPORT.getKey()); - } - - @Override - public String getFeedFileName() { - return getAsString(FEED_FILE.getKey()); - } - - @Override - public String getIgnoreFileName() { - return getAsString(IGNORE_FILE.getKey()); - } - - @Override - public String getIndexFileName() { - return getAsString(INDEX_FILE.getKey()); - } - - @Override - public Iterator getKeys() { - return compositeConfiguration.getKeys(); - } - - @Override - public List getMarkdownExtensions() { - return getAsList(MARKDOWN_EXTENSIONS.getKey()); - } - - public void setMarkdownExtensions(String... extensions) { - setProperty(MARKDOWN_EXTENSIONS.getKey(), StringUtils.join(extensions, ",")); - } - - @Override - public String getOutputExtension() { - return getAsString(OUTPUT_EXTENSION.getKey()); - } - - public void setOutputExtension(String outputExtension) { - setProperty(OUTPUT_EXTENSION.getKey(), outputExtension); - } - - @Override - public String getOutputExtensionByDocType(String docType) { - String templateExtensionKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_EXTENSION_POSTFIX; - String defaultOutputExtension = getOutputExtension(); - return getAsString(templateExtensionKey, defaultOutputExtension); - } - - @Override - public boolean getPaginateIndex() { - return getAsBoolean(PAGINATE_INDEX.getKey()); - } - - public void setPaginateIndex(boolean paginateIndex) { - setProperty(PAGINATE_INDEX.getKey(), paginateIndex); - } - - @Override - public int getPostsPerPage() { - return getAsInt(POSTS_PER_PAGE.getKey(), 5); - } - - public void setPostsPerPage(int postsPerPage) { - setProperty(POSTS_PER_PAGE.getKey(), postsPerPage); - } - - @Override - public String getPrefixForUriWithoutExtension() { - return getAsString(URI_NO_EXTENSION_PREFIX.getKey()); - } - - public void setPrefixForUriWithoutExtension(String prefix) { - setProperty(URI_NO_EXTENSION_PREFIX.getKey(), prefix); - } - - @Override - public boolean getRenderArchive() { - return getAsBoolean(RENDER_ARCHIVE.getKey()); - } - - @Override - public String getRenderEncoding() { - return getAsString(RENDER_ENCODING.getKey()); - } - - @Override - public String getOutputEncoding() { - return getAsString(OUTPUT_ENCODING.getKey()); - } - - @Override - public boolean getRenderError404() { - return getAsBoolean(RENDER_ERROR404.getKey()); - } - - @Override - public boolean getRenderFeed() { - return getAsBoolean(RENDER_FEED.getKey()); - } - - @Override - public boolean getRenderIndex() { - return getAsBoolean(RENDER_INDEX.getKey()); - } - - @Override - public boolean getRenderSiteMap() { - return getAsBoolean(RENDER_SITEMAP.getKey()); - } - - @Override - public boolean getRenderTags() { - return getAsBoolean(RENDER_TAGS.getKey()); - } - - @Override - public boolean getRenderTagsIndex() { - return compositeConfiguration.getBoolean(RENDER_TAGS_INDEX.getKey(), false); - } - - public void setRenderTagsIndex(boolean enable) { - compositeConfiguration.setProperty(RENDER_TAGS_INDEX.getKey(), enable); - } - - @Override - public boolean getSanitizeTag() { - return getAsBoolean(TAG_SANITIZE.getKey()); - } - - @Override - public int getServerPort() { - return getAsInt(SERVER_PORT.getKey(), 8080); - } - - public void setServerPort(int port) { - setProperty(SERVER_PORT.getKey(), port); - } - - @Override - public String getSiteHost() { - return getAsString(SITE_HOST.getKey(), "http://www.jbake.org"); - } - - public void setSiteHost(String siteHost) { - setProperty(SITE_HOST.getKey(), siteHost); - } - - @Override - public String getSiteMapFileName() { - return getAsString(SITEMAP_FILE.getKey()); - } - - @Override - public File getSourceFolder() { - return getAsFolder(SOURCE_FOLDER_KEY); - } - - public void setSourceFolder(File sourceFolder) { - setProperty(SOURCE_FOLDER_KEY, sourceFolder); - setupPaths(); - } - - @Override - public String getTagPathName() { - return getAsString(TAG_PATH.getKey()); - } - - @Override - public String getTemplateEncoding() { - return getAsString(TEMPLATE_ENCODING.getKey()); - } - - @Override - public String getTemplateByDocType(String docType) { - String templateKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_FILE_POSTFIX; - String templateFileName = getAsString(templateKey); - if (templateFileName != null) { - return templateFileName; - } - logger.warn("Cannot find configuration key '{}' for document type '{}'", templateKey, docType); - return null; - } - - @Override - public File getTemplateFileByDocType(String docType) { - String templateFileName = getTemplateByDocType(docType); - if (templateFileName != null) { - return new File(getTemplateFolder(), templateFileName); - } - return null; - } - - @Override - public File getTemplateFolder() { - return getAsFolder(TEMPLATE_FOLDER_KEY); - } - - public void setTemplateFolder(File templateFolder) { - if (templateFolder != null) { - setProperty(TEMPLATE_FOLDER_KEY, templateFolder); - } - } - - @Override - public String getTemplateFolderName() { - return getAsString(TEMPLATE_FOLDER.getKey()); - } - - @Override - public String getThymeleafLocale() { - return getAsString(THYMELEAF_LOCALE.getKey()); - } - - @Override - public boolean getUriWithoutExtension() { - return getAsBoolean(URI_NO_EXTENSION.getKey()); - } - - public void setUriWithoutExtension(boolean withoutExtension) { - setProperty(URI_NO_EXTENSION.getKey(), withoutExtension); - } - - @Override - public String getVersion() { - return getAsString(VERSION.getKey()); - } - - public void setDestinationFolderName(String folderName) { - setProperty(DESTINATION_FOLDER.getKey(), folderName); - setupDefaultDestination(); - } - - public void setExampleProject(String type, String fileName) { - String projectKey = "example.project." + type; - setProperty(projectKey, fileName); - } - - @Override - public void setProperty(String key, Object value) { - - compositeConfiguration.setProperty(key, value); - } - - @Override - public String getThymeleafModeByType(String type) { - String key = "template_" + type + "_thymeleaf_mode"; - return getAsString(key, DEFAULT_TYHMELEAF_TEMPLATE_MODE); - } - - @Override - public String getServerContextPath() { - return getAsString(SERVER_CONTEXT_PATH.getKey()); - } - - @Override - public String getServerHostname() { - return getAsString(SERVER_HOSTNAME.getKey()); - } - - @Override - public Map asHashMap() { - HashMap configModel = new HashMap<>(); - Iterator configKeys = this.getKeys(); - while (configKeys.hasNext()) { - String key = configKeys.next(); - Object valueObject; - - if (key.equals(PAGINATE_INDEX.getKey())) { - valueObject = this.getPaginateIndex(); - } else { - valueObject = this.get(key); - } - //replace "." in key so you can use dot notation in templates - configModel.put(key.replace(".", "_"), valueObject); - } - return configModel; - } - - public void setTemplateExtensionForDocType(String docType, String extension) { - String templateExtensionKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_EXTENSION_POSTFIX; - setProperty(templateExtensionKey, extension); - } - - public void setTemplateFileNameForDocType(String docType, String fileName) { - String templateKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_FILE_POSTFIX; - setProperty(templateKey, fileName); - } - - private void setupPaths() { - setupDefaultDestination(); - setupDefaultAssetFolder(); - setupDefaultTemplateFolder(); - setupDefaultContentFolder(); - setupDefaultDataFolder(); - } - - private void setupDefaultDestination() { - String destinationPath = getAsString(DESTINATION_FOLDER.getKey()); - - File destination = new File(destinationPath); - if ( destination.isAbsolute() ) { - setDestinationFolder(destination); - } else { - setDestinationFolder(new File(getSourceFolder(), destinationPath)); - } - } - - private void setupDefaultAssetFolder() { - String assetFolder = getAsString(ASSET_FOLDER.getKey()); - - - File asset = new File(assetFolder); - if(asset.isAbsolute()) { - setAssetFolder(asset); - } else { - setAssetFolder(new File(getSourceFolder(), assetFolder)); - } - } - - private void setupDefaultTemplateFolder() { - String templateFolder = getAsString(TEMPLATE_FOLDER.getKey()); - - File template = new File(templateFolder); - if(template.isAbsolute()) { - setTemplateFolder(template); - } else { - setTemplateFolder(new File(getSourceFolder(), templateFolder)); - } - } - - private void setupDefaultDataFolder() { - String dataFolder = getAsString(DATA_FOLDER.getKey()); - - File data = new File(dataFolder); - if(data.isAbsolute()) { - setDataFolder(data); - } else { - setDataFolder(new File(getSourceFolder(), dataFolder)); - } - } - - private void setupDefaultContentFolder() { - setContentFolder(new File(getSourceFolder(), getContentFolderName())); - } - - @Override - public String getHeaderSeparator() { - return getAsString(HEADER_SEPARATOR.getKey()); - } - - public void setHeaderSeparator(String headerSeparator) { - setProperty(HEADER_SEPARATOR.getKey(), headerSeparator); - } - - @Override - public boolean getImgPathPrependHost() { - return getAsBoolean(IMG_PATH_PREPEND_HOST.getKey()); - } - - public void setImgPathPrependHost(boolean imgPathPrependHost) { - setProperty(IMG_PATH_PREPEND_HOST.getKey(), imgPathPrependHost); - } - - @Override - public boolean getImgPathUpdate() { - return getAsBoolean(IMG_PATH_UPDATE.getKey()); - } - - public void setImgPathUPdate(boolean imgPathUpdate) { - setProperty(IMG_PATH_UPDATE.getKey(), imgPathUpdate); - } - - public List getJbakeProperties() { - - List jbakeKeys = new ArrayList<>(); - - for (int i = 0; i < compositeConfiguration.getNumberOfConfigurations(); i++) { - Configuration configuration = compositeConfiguration.getConfiguration(i); - - if (!(configuration instanceof SystemConfiguration)) { - for (Iterator it = configuration.getKeys(); it.hasNext(); ) { - String key = it.next(); - Property property = PropertyList.getPropertyByKey(key); - if (!jbakeKeys.contains(property)) { - jbakeKeys.add(property); - } - } - } - } - Collections.sort(jbakeKeys); - return jbakeKeys; - } - - @Override - public void addConfiguration(Properties properties) { - compositeConfiguration.addConfiguration(new MapConfiguration(properties)); - } - - @Override - public String getAbbreviatedGitHash() { - return getAsString(GIT_HASH.getKey()); - } - - @Override - public String getJvmLocale() { - return getAsString(JVM_LOCALE.getKey()); - } - - @Override - public TimeZone getFreemarkerTimeZone() { - String timezone = getAsString(FREEMARKER_TIMEZONE.getKey()); - if (StringUtils.isNotEmpty(timezone)) { - return TimeZone.getTimeZone(timezone); - } - return null; - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java b/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java deleted file mode 100644 index 98e2625db..000000000 --- a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java +++ /dev/null @@ -1,383 +0,0 @@ -package org.jbake.app.configuration; - -import java.io.File; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.TimeZone; - -/** - * JBakeConfiguration gives you access to the project configuration. Typically located in a file called jbake.properties. - * - * Use one of {@link JBakeConfigurationFactory} methods to create an instance. - */ -public interface JBakeConfiguration { - - /** - * Get property value by a given key from the configuration - * - * @param key a key for the property like site.host - * @return the value of the property - */ - Object get(String key); - - /** - * @return Output filename for archive file, is only used when {@link #getRenderArchive()} is true - */ - String getArchiveFileName(); - - /** - * @return attributes to be set when processing input - */ - List getAsciidoctorAttributes(); - - /** - * Get an asciidoctor option by it's key - * - * @param optionKey an option key - * @return the value of the option key - */ - Object getAsciidoctorOption(String optionKey); - - /** - * Get a list of asciidoctor options - * - * @return list of asciidoctor options - */ - List getAsciidoctorOptionKeys(); - - /** - * @return the folder where assets are stored, they are copied directly in output folder and not processed - */ - File getAssetFolder(); - - /** - * @return name of folder for assets - */ - String getAssetFolderName(); - - /** - * @return Flag indicating if hidden asset resources should be ignored - */ - boolean getAssetIgnoreHidden(); - - /** - * @return Prefix to be used when exporting JBake properties to Asciidoctor - */ - String getAttributesExportPrefixForAsciidoctor(); - - /** - * @return Timestamp that records when JBake build was made - */ - String getBuildTimeStamp(); - - /** - * @return Flag indicating to flash the database cache - */ - boolean getClearCache(); - - /** - * @return the content folder - */ - File getContentFolder(); - - /** - * @return name of Folder where content (that's to say files to be transformed) resides in - */ - String getContentFolderName(); - - /** - * @return the data folder - */ - File getDataFolder(); - - /** - * @return name of Folder where data files reside in - */ - String getDataFolderName(); - - /** - * @return docType for data files - */ - String getDataFileDocType(); - - /** - * @return Folder to store database files in - */ - String getDatabasePath(); - - /** - * @return name to identify if database is kept in memory (memory) or persisted to disk (plocal) - */ - String getDatabaseStore(); - - /** - * @return How date is formated - */ - String getDateFormat(); - - /** - * @return Default status to use (in order to avoid putting it in all files) - */ - String getDefaultStatus(); - - /** - * @return Default type to use (in order to avoid putting it in all files) - */ - String getDefaultType(); - - /** - * @return The destination folder to render and copy files to - */ - File getDestinationFolder(); - - void setDestinationFolder(File destination); - - List getDocumentTypes(); - - /** - * @return Suffix used to identify draft files - */ - String getDraftSuffix(); - - /** - * @return Output filename for error404 file, is only used when {@link #getRenderError404()} is true - */ - String getError404FileName(); - - /** - * Get name for example project name by given template type - * - * @param templateType a template type - * @return example project name - */ - String getExampleProjectByType(String templateType); - - /** - * @return Flag indicating if JBake properties should be made available to Asciidoctor - */ - boolean getExportAsciidoctorAttributes(); - - /** - * @return Output filename for feed file, is only used when {@link #getRenderFeed()} is true - */ - String getFeedFileName(); - - - /** - * @return String used to separate the header from the body - */ - String getHeaderSeparator(); - - /** - * @return Filename to use to ignore a directory in addition to ".jbakeignore" - */ - String getIgnoreFileName(); - - /** - * @return Output filename for index, is only used when {@link #getRenderIndex()} is true - */ - String getIndexFileName(); - - /** - * Get an iterator of available configuration keys - * - * @return an iterator of configuration keys - */ - Iterator getKeys(); - - /** - * A list of markdown extensions - *

- * markdown.extension=HARDWRAPS,AUTOLINKS,FENCED_CODE_BLOCKS,DEFINITIONS - * - * @return list of markdown extensions as string - */ - List getMarkdownExtensions(); - - /** - * @return file extension to be used for all output files - */ - String getOutputExtension(); - - String getOutputExtensionByDocType(String docType); - - /** - * @return Flag indicating if there should be pagination when rendering index - */ - boolean getPaginateIndex(); - - /** - * @return How many posts per page on index - */ - int getPostsPerPage(); - - /** - * @return URI prefix for content that should be given extension-less output URI's - */ - String getPrefixForUriWithoutExtension(); - - /** - * @return Flag indicating if archive file should be generated - */ - boolean getRenderArchive(); - - /** - * @return Encoding used when rendering files - */ - String getRenderEncoding(); - - /** - * @return Output encoding for freemarker url escaping - */ - String getOutputEncoding(); - - /** - * @return Flag indicating if error404 file should be generated - */ - boolean getRenderError404(); - - /** - * @return Flag indicating if feed file should be generated - */ - boolean getRenderFeed(); - - /** - * @return Flag indicating if index file should be generated - */ - boolean getRenderIndex(); - - /** - * @return Flag indicating if sitemap file should be generated - */ - boolean getRenderSiteMap(); - - /** - * @return Flag indicating if tag files should be generated - */ - boolean getRenderTags(); - - /** - * @return Flag indicating if tag index file should be generated - */ - boolean getRenderTagsIndex(); - - /** - * @return Flag indicating if the tag value should be sanitized - */ - boolean getSanitizeTag(); - - /** - * @return Port used when running Jetty server - */ - int getServerPort(); - - /** - * @return the host url of the site e.g. http://jbake.org - */ - String getSiteHost(); - - /** - * @return Sitemap template file name. Used only when {@link #getRenderSiteMap()} is set to true - */ - String getSiteMapFileName(); - - /** - * @return the source folder of the project - */ - File getSourceFolder(); - - /** - * @return Tags output path, used only when {@link #getRenderTags()} is true - */ - String getTagPathName(); - - /** - * @return Encoding to be used for template files - */ - String getTemplateEncoding(); - - String getTemplateByDocType(String doctype); - - File getTemplateFileByDocType(String doctype); - - /** - * @return the template folder - */ - File getTemplateFolder(); - - /** - * @return name of folder where template files are looked for - */ - String getTemplateFolderName(); - - /** - * @return Locale used for Thymeleaf template rendering - */ - String getThymeleafLocale(); - - /** - * @return Flag indicating if content matching prefix below should be given extension-less URI's - */ - boolean getUriWithoutExtension(); - - /** - * @return Flag indicating if image paths should be prepended with {@link #getSiteHost()} value - only has an effect if - * {@link #getImgPathUpdate()} is set to true - */ - boolean getImgPathPrependHost(); - - /** - * @return Flag indicating if image paths in content should be updated with absolute path (using URI value of content file), - * see {@link #getImgPathUpdate()} which allows you to control the absolute path used - */ - boolean getImgPathUpdate(); - - /** - * @return Version of JBake - */ - String getVersion(); - - /** - * Set a property value for the given key - * - * @param key the key for the property - * @param value the value of the property - */ - void setProperty(String key, Object value); - - /** - * - * @param type the documents type - * @return the the thymeleaf render mode ( defaults to {@link DefaultJBakeConfiguration#DEFAULT_TYHMELEAF_TEMPLATE_MODE} ) - */ - String getThymeleafModeByType(String type); - - String getServerContextPath(); - - String getServerHostname(); - - /** - * @return Abbreviated hash of latest git commit - */ - String getAbbreviatedGitHash(); - - /** - * @return Locale to set in the JVM - */ - String getJvmLocale(); - - /** - * - * @return TimeZone to use within Freemarker - */ - TimeZone getFreemarkerTimeZone(); - - Map asHashMap(); - - List getJbakeProperties(); - - void addConfiguration(Properties properties); -} - diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfigurationFactory.java b/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfigurationFactory.java deleted file mode 100644 index 1ff3fa764..000000000 --- a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfigurationFactory.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.jbake.app.configuration; - - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.jbake.app.JBakeException; - -import java.io.File; - -/** - * A {@link JBakeConfiguration} factory - */ -public class JBakeConfigurationFactory { - - private ConfigUtil configUtil; - - public JBakeConfigurationFactory() { - this.configUtil = new ConfigUtil(); - } - - /** - * Creates a {@link DefaultJBakeConfiguration} using default.properties and jbake.properties - * - * @param sourceFolder The source folder of the project - * @param destination The destination folder to render and copy files to - * @param isClearCache Whether to clear database cache or not - * @return A configuration by given parameters - * @throws JBakeException if loading the configuration fails - */ - public DefaultJBakeConfiguration createDefaultJbakeConfiguration(File sourceFolder, File destination, boolean isClearCache) throws JBakeException { - return createDefaultJbakeConfiguration(sourceFolder, destination, (File) null, isClearCache); - } - - /** - * Creates a {@link DefaultJBakeConfiguration} - * @param sourceFolder The source folder of the project - * @param destination The destination folder to render and copy files to - * @param propertiesFile The properties file for the project - * @param isClearCache Whether to clear database cache or not - * @return A configuration by given parameters - * @throws JBakeException if loading the configuration fails - */ - public DefaultJBakeConfiguration createDefaultJbakeConfiguration(File sourceFolder, File destination, File propertiesFile, boolean isClearCache) throws JBakeException { - DefaultJBakeConfiguration configuration = (DefaultJBakeConfiguration) getConfigUtil().loadConfig(sourceFolder, propertiesFile); - configuration.setDestinationFolder(destination); - configuration.setClearCache(isClearCache); - return configuration; - } - - /** - * Creates a {@link DefaultJBakeConfiguration} - * - * This is a compatibility factory method - * - * @param sourceFolder The source folder of the project - * @param destination The destination folder to render and copy files to - * @param compositeConfiguration A given {@link CompositeConfiguration} - * @param isClearCache Whether to clear database cache or not - * @return A configuration by given parameters - * @deprecated use {@link #createDefaultJbakeConfiguration(File, File, File, boolean)} instead - */ - @Deprecated - public DefaultJBakeConfiguration createDefaultJbakeConfiguration(File sourceFolder, File destination, CompositeConfiguration compositeConfiguration, boolean isClearCache) throws JBakeException { - DefaultJBakeConfiguration configuration = new DefaultJBakeConfiguration(sourceFolder, compositeConfiguration); - configuration.setDestinationFolder(destination); - configuration.setClearCache(isClearCache); - return configuration; - } - - /** - * Creates a {@link DefaultJBakeConfiguration} - * - * This is a compatibility factory method - * - * @param sourceFolder The source folder of the project - * @param destination The destination folder to render and copy files to - * @param compositeConfiguration A given {@link CompositeConfiguration} - * @return A configuration by given parameters - * @deprecated use {@link #createDefaultJbakeConfiguration(File, File, File, boolean)} instead - */ - @Deprecated - public DefaultJBakeConfiguration createDefaultJbakeConfiguration(File sourceFolder, File destination, CompositeConfiguration compositeConfiguration) throws JBakeException { - DefaultJBakeConfiguration configuration = new DefaultJBakeConfiguration(sourceFolder, compositeConfiguration); - configuration.setDestinationFolder(destination); - return configuration; - } - - /** - * Creates a {@link DefaultJBakeConfiguration} - * - * - * @param sourceFolder The source folder of the project - * @param config A {@link CompositeConfiguration} - * @return A configuration by given parameters - */ - @Deprecated - public DefaultJBakeConfiguration createDefaultJbakeConfiguration(File sourceFolder, CompositeConfiguration config) throws JBakeException { - return new DefaultJBakeConfiguration(sourceFolder,config); - } - - /** - * Creates a {@link DefaultJBakeConfiguration} with value site.host replaced - * by http://localhost:[server.port]. - * The server.port is read from the project properties file. - * - * @param sourceFolder The source folder of the project - * @param destinationFolder The destination folder to render and copy files to - * @param isClearCache Whether to clear database cache or not - * @return A configuration by given parameters - * @throws JBakeException if loading the configuration fails - * @deprecated use {@link #createJettyJbakeConfiguration(File, File, File, boolean)} instead - */ - @Deprecated - public DefaultJBakeConfiguration createJettyJbakeConfiguration(File sourceFolder, File destinationFolder, boolean isClearCache) throws JBakeException { - return createJettyJbakeConfiguration(sourceFolder, destinationFolder, (File)null, isClearCache); - } - - /** - * Creates a {@link DefaultJBakeConfiguration} with value site.host replaced - * by http://localhost:[server.port]. - * The server.port is read from the project properties file. - * - * @param sourceFolder The source folder of the project - * @param destinationFolder The destination folder to render and copy files to - * @param propertiesFile The properties file for the project - * @param isClearCache Whether to clear database cache or not - * @return A configuration by given parameters - * @throws JBakeException if loading the configuration fails - */ - public DefaultJBakeConfiguration createJettyJbakeConfiguration(File sourceFolder, File destinationFolder, File propertiesFile, boolean isClearCache) throws JBakeException { - DefaultJBakeConfiguration configuration = (DefaultJBakeConfiguration) getConfigUtil().loadConfig(sourceFolder, propertiesFile); - configuration.setDestinationFolder(destinationFolder); - configuration.setClearCache(isClearCache); - configuration.setSiteHost("http://" + configuration.getServerHostname() + ":" +configuration.getServerPort() + configuration.getServerContextPath()); - return configuration; - } - - public ConfigUtil getConfigUtil() { - return configUtil; - } - - public void setConfigUtil(ConfigUtil configUtil) { - this.configUtil = configUtil; - } - - public JBakeConfigurationFactory setEncoding(String charset) { - this.configUtil.setEncoding(charset); - return this; - } -} diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfigurationInspector.java b/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfigurationInspector.java deleted file mode 100644 index abe3737cf..000000000 --- a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfigurationInspector.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.jbake.app.configuration; - -import org.jbake.app.FileUtil; -import org.jbake.app.JBakeException; -import org.jbake.launcher.SystemExit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - -import static org.jbake.app.configuration.PropertyList.CONTENT_FOLDER; -import static org.jbake.app.configuration.PropertyList.TEMPLATE_FOLDER; - -public class JBakeConfigurationInspector { - - private static final Logger LOGGER = LoggerFactory.getLogger(JBakeConfigurationInspector.class); - - private final JBakeConfiguration configuration; - - public JBakeConfigurationInspector(JBakeConfiguration configuration) { - this.configuration = configuration; - } - - public void inspect() throws JBakeException { - ensureSource(); - ensureTemplateFolder(); - ensureContentFolder(); - ensureDestination(); - checkAssetFolder(); - } - - private void ensureSource() throws JBakeException { - File source = configuration.getSourceFolder(); - if (!FileUtil.isExistingFolder(source)) { - throw new JBakeException(SystemExit.CONFIGURATION_ERROR, "Error: Source folder must exist: " + source.getAbsolutePath()); - } - if (!configuration.getSourceFolder().canRead()) { - throw new JBakeException(SystemExit.CONFIGURATION_ERROR, "Error: Source folder is not readable: " + source.getAbsolutePath()); - } - } - - private void ensureTemplateFolder() { - File path = configuration.getTemplateFolder(); - checkRequiredFolderExists(TEMPLATE_FOLDER.getKey(), path); - } - - private void ensureContentFolder() { - File path = configuration.getContentFolder(); - checkRequiredFolderExists(CONTENT_FOLDER.getKey(), path); - } - - private void ensureDestination() { - File destination = configuration.getDestinationFolder(); - if (!destination.exists()) { - destination.mkdirs(); - } - if (!destination.canWrite()) { - throw new JBakeException(SystemExit.CONFIGURATION_ERROR, "Error: Destination folder is not writable: " + destination.getAbsolutePath()); - } - } - - private void checkAssetFolder() { - File path = configuration.getAssetFolder(); - if (!path.exists()) { - LOGGER.warn("No asset folder '{}' was found!", path.getAbsolutePath()); - } - } - - private void checkRequiredFolderExists(String folderName, File path) { - if (!FileUtil.isExistingFolder(path)) { - throw new JBakeException(SystemExit.CONFIGURATION_ERROR, "Error: Required folder cannot be found! Expected to find [" + folderName + "] at: " + path.getAbsolutePath()); - } - } - - -} diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/Property.java b/jbake-core/src/main/java/org/jbake/app/configuration/Property.java deleted file mode 100644 index 3ea349763..000000000 --- a/jbake-core/src/main/java/org/jbake/app/configuration/Property.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.jbake.app.configuration; - -import java.util.Objects; - -public class Property implements Comparable { - - private final String key; - private final String description; - private final Group group; - - public Property(String key, String description) { - this(key, description, Group.DEFAULT); - } - - public Property(String key, String description, Group group) { - this.key = key; - this.description = description; - this.group = group; - } - - public String getKey() { - return key; - } - - public String getDescription() { - return description; - } - - public Group getGroup() { - return group; - } - - @Override - public String toString() { - return getKey(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Property property = (Property) o; - return Objects.equals(key, property.key) && - Objects.equals(description, property.description) && - group == property.group; - } - - @Override - public int hashCode() { - return Objects.hash(key, description, group); - } - - @Override - public int compareTo(Property other) { - int result = this.getGroup().compareTo(other.getGroup()); - - if (result == 0) { - result = this.getKey().compareTo(other.getKey()); - } - return result; - } - - public enum Group { - DEFAULT, CUSTOM - } - - -} diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java b/jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java deleted file mode 100644 index 99db20081..000000000 --- a/jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java +++ /dev/null @@ -1,371 +0,0 @@ -package org.jbake.app.configuration; - -import java.lang.reflect.Field; - -import static org.jbake.app.configuration.Property.Group.CUSTOM; - -public abstract class PropertyList { - - public static final Property ARCHIVE_FILE = new Property( - "archive.file", - "Output filename for archive file" - ); - - public static final Property ASCIIDOCTOR_ATTRIBUTES = new Property( - "asciidoctor.attributes", - "attributes to be set when processing input" - ); - - public static final Property ASCIIDOCTOR_ATTRIBUTES_EXPORT = new Property( - "asciidoctor.attributes.export", - "Prefix to be used when exporting JBake properties to Asciidoctor" - ); - - public static final Property ASCIIDOCTOR_ATTRIBUTES_EXPORT_PREFIX = new Property( - "asciidoctor.attributes.export.prefix", - "prefix that should be used when JBake config options are exported" - ); - - public static final Property ASCIIDOCTOR_OPTION = new Property( - "asciidoctor.option", - "default asciidoctor options" - ); - - public static final Property ASSET_FOLDER = new Property( - "asset.folder", - "folder that contains all asset files" - ); - - public static final Property ASSET_IGNORE_HIDDEN = new Property( - "asset.ignore", - "Flag indicating if hidden asset resources should be ignored" - ); - - public static final Property BUILD_TIMESTAMP = new Property( - "build.timestamp", - "timestamp jbake was build"); - - public static final Property CLEAR_CACHE = new Property( - "db.clear.cache", - "clear database cache" - ); - - public static final Property CONTENT_FOLDER = new Property( - "content.folder", - "folder that contains all content files" - ); - - public static final Property DATA_FOLDER = new Property( - "data.folder", - "folder that contains all data files" - ); - - public static final Property DATA_FILE_DOCTYPE = new Property( - "data.file.docType", - "document type to use for data files" - ); - - public static final Property DATE_FORMAT = new Property( - "date.format", - "default date format used in content files" - ); - - public static final Property DB_STORE = new Property( - "db.store", - "database store (plocal, memory)" - ); - - public static final Property DB_PATH = new Property( - "db.path", - "database path for persistent storage" - ); - - public static final Property DEFAULT_STATUS = new Property( - "default.status", - "default document status" - ); - - public static final Property DEFAULT_TYPE = new Property( - "default.type", - "default document type" - ); - - public static final Property DESTINATION_FOLDER = new Property( - "destination.folder", - "path to destination folder by default" - ); - - public static final Property DRAFT_SUFFIX = new Property( - "draft.suffix", - "draft content suffix" - ); - - public static final Property ERROR404_FILE = new Property( - "error404.file", - "filename to use for 404 error" - ); - - public static final Property FEED_FILE = new Property( - "feed.file", - "filename to use for feed" - ); - - public static final Property FREEMARKER_TIMEZONE = new Property( - "freemarker.timezone", - "TimeZone to use within Freemarker" - ); - - public static final Property GIT_HASH = new Property( - "git.hash", - "abbreviated git hash" - ); - - public static final Property HEADER_SEPARATOR = new Property( - "header.separator", - "String used to separate the header from the body" - ); - - public static final Property IGNORE_FILE = new Property( - "ignore.file", - "file used to ignore a directory" - ); - - public static final Property IMG_PATH_UPDATE = new Property( - "img.path.update", - "update image path?" - ); - - public static final Property IMG_PATH_PREPEND_HOST = new Property( - "img.path.prepend.host", - "Prepend site.host to image paths" - ); - - public static final Property INDEX_FILE = new Property( - "index.file", - "filename to use for index file" - ); - - public static final Property JVM_LOCALE = new Property( - "jvm.locale", - "locale for the jvm" - ); - - public static final Property MARKDOWN_EXTENSIONS = new Property( - "markdown.extensions", - "comma delimited default markdown extensions; for available extensions: http://www.decodified.com/pegdown/api/org/pegdown/Extensions.html" - ); - - public static final Property OUTPUT_ENCODING = new Property( - "freemarker.outputencoding", - "default output_encoding setting for freemarker" - ); - - public static final Property OUTPUT_EXTENSION = new Property( - "output.extension", - "file extension for output content files" - ); - - public static final Property PAGINATE_INDEX = new Property( - "index.paginate", - "paginate index?" - ); - - public static final Property POSTS_PER_PAGE = new Property( - "index.posts_per_page", - "number of post per page for pagination" - ); - - public static final Property RENDER_ARCHIVE = new Property( - "render.archive", - "render archive file?" - ); - - public static final Property RENDER_ENCODING = new Property( - "render.encoding", - "character encoding MIME name used in templates. use one of http://www.iana.org/assignments/character-sets/character-sets.xhtml" - ); - - public static final Property RENDER_ERROR404 = new Property( - "render.error404", - "render 404 page?" - ); - - public static final Property RENDER_FEED = new Property( - "render.feed", - "render feed file?" - ); - - public static final Property RENDER_INDEX = new Property( - "render.index", - "render index file?" - ); - - public static final Property RENDER_SITEMAP = new Property( - "render.sitemap", - "render sitemap.xml file?" - ); - - public static final Property RENDER_TAGS = new Property( - "render.tags", - "render tag files?" - ); - - public static final Property RENDER_TAGS_INDEX = new Property( - "render.tagsindex", - "render tag index file?" - ); - - public static final Property SERVER_PORT = new Property( - "server.port", - "default server port" - ); - - public static final Property SERVER_HOSTNAME = new Property( - "server.hostname", - "default server hostname" - ); - - public static final Property SERVER_CONTEXT_PATH = new Property( - "server.contextPath", - "default server context path" - ); - - public static final Property SITE_HOST = new Property( - "site.host", - "site host" - ); - - public static final Property SITEMAP_FILE = new Property( - "sitemap.file", - "filename to use for sitemap file" - ); - - public static final Property TAG_SANITIZE = new Property( - "tag.sanitize", - "sanitize tag value before it is used as filename (i.e. replace spaces with hyphens)" - ); - - public static final Property TAG_PATH = new Property( - "tag.path", - "folder name to use for tag files" - ); - - public static final Property TEMPLATE_FOLDER = new Property( - "template.folder", - "folder that contains all template files" - ); - - public static final Property TEMPLATE_ENCODING = new Property( - "template.encoding", - "character encoding MIME name used in templates. use one of http://www.iana.org/assignments/character-sets/character-sets.xhtml" - ); - - public static final Property TEMPLATE_MASTERINDEX_FILE = new Property( - "template.masterindex.file", - "filename of masterindex template file" - ); - - public static final Property TEMPLATE_FEED_FILE = new Property( - "template.feed.file", - "filename of feed template file" - ); - - public static final Property TEMPLATE_ARCHIVE_FILE = new Property( - "template.archive.file", - "filename of archive template file" - ); - - public static final Property TEMPLATE_TAG_FILE = new Property( - "template.tag.file", - "filename of tag template file" - ); - - public static final Property TEMPLATE_TAGSINDEX_FILE = new Property( - "template.tagsindex.file", - "filename of tag index template file" - ); - - public static final Property TEMPLATE_SITEMAP_FILE = new Property( - "template.sitemap.file", - "filename of sitemap template file" - ); - - public static final Property TEMPLATE_POST_FILE = new Property( - "template.post.file", - "filename of post template file" - ); - - public static final Property TEMPLATE_PAGE_FILE = new Property( - "template.page.file", - "filename of page template file" - ); - - public static final Property EXAMPLE_PROJECT_FREEMARKER = new Property( - "example.project.freemarker", - "zip file containing example project structure using freemarker templates" - ); - - public static final Property EXAMPLE_PROJECT_GROOVY = new Property( - "example.project.groovy", - "zip file containing example project structure using groovy templates" - ); - - public static final Property EXAMPLE_PROJECT_GROOVY_MTE = new Property( - "example.project.groovy-mte", - "zip file containing example project structure using groovy markup templates" - ); - - public static final Property EXAMPLE_PROJECT_THYMELEAF = new Property( - "example.project.thymeleaf", - "zip file containing example project structure using thymeleaf templates" - ); - - public static final Property EXAMPLE_PROJECT_JADE = new Property( - "example.project.jade", - "zip file containing example project structure using jade templates" - ); - - public static final Property MARKDOWN_MAX_PARSINGTIME = new Property( - "markdown.maxParsingTimeInMillis", - "millis to parse single markdown page. See PegDown Parse configuration for details" - ); - - public static final Property THYMELEAF_LOCALE = new Property( - "thymeleaf.locale", - "default thymeleafe locale" - ); - - public static final Property URI_NO_EXTENSION = new Property( - "uri.noExtension", - "enable extension-less URI option?" - ); - - public static final Property URI_NO_EXTENSION_PREFIX = new Property( - "uri.noExtension.prefix", - "Set to a prefix path (starting with a slash) for which to generate extension-less URI's (i.e. a folder with index.html in)" - ); - - public static final Property VERSION = new Property( - "version", - "jbake application version" - ); - - private PropertyList() { - } - - public static Property getPropertyByKey(String key) { - - for (Field field : PropertyList.class.getFields()) { - try { - Property property = (Property) field.get(null); - - if (property.getKey().equals(key)) { - return property; - } - } catch (IllegalAccessException e) { - return new Property(key, "", CUSTOM); - } - } - return new Property(key, "", CUSTOM); - } -} diff --git a/jbake-core/src/main/java/org/jbake/launcher/BakeWatcher.java b/jbake-core/src/main/java/org/jbake/launcher/BakeWatcher.java deleted file mode 100644 index 3512cb83d..000000000 --- a/jbake-core/src/main/java/org/jbake/launcher/BakeWatcher.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.jbake.launcher; - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.apache.commons.vfs2.FileObject; -import org.apache.commons.vfs2.FileSystemException; -import org.apache.commons.vfs2.FileSystemManager; -import org.apache.commons.vfs2.VFS; -import org.apache.commons.vfs2.impl.DefaultFileMonitor; -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.app.configuration.JBakeConfigurationFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Delegate responsible for watching the file system for changes. - * - * @author jmcgarr@gmail.com - */ -public class BakeWatcher { - - private final Logger logger = LoggerFactory.getLogger(BakeWatcher.class); - - /** - * Starts watching the file system for changes to trigger a bake. - * - * @deprecated use {@link BakeWatcher#start(JBakeConfiguration)} instead - * - * @param res Commandline options - * @param config Configuration settings - */ - @Deprecated - public void start(final LaunchOptions res, CompositeConfiguration config) { - JBakeConfiguration configuration = new JBakeConfigurationFactory().createDefaultJbakeConfiguration(res.getSource(), config); - start(configuration); - } - - /** - * Starts watching the file system for changes to trigger a bake. - * - * @param config JBakeConfiguration settings - */ - public void start(JBakeConfiguration config) { - try { - FileSystemManager fsMan = VFS.getManager(); - FileObject listenPath = fsMan.resolveFile(config.getContentFolder().toURI()); - FileObject templateListenPath = fsMan.resolveFile(config.getTemplateFolder().toURI()); - FileObject assetPath = fsMan.resolveFile(config.getAssetFolder().toURI()); - FileObject dataPath = fsMan.resolveFile(config.getDataFolder().toURI()); - - logger.info("Watching for (content, data, template, asset) changes in [{}]", config.getSourceFolder().getPath()); - DefaultFileMonitor monitor = new DefaultFileMonitor(new CustomFSChangeListener(config)); - monitor.setRecursive(true); - monitor.addFile(listenPath); - monitor.addFile(templateListenPath); - monitor.addFile(assetPath); - monitor.addFile(dataPath); - monitor.start(); - } catch (FileSystemException e) { - logger.error("Problems watching filesystem changes", e); - } - } -} diff --git a/jbake-core/src/main/java/org/jbake/launcher/Baker.java b/jbake-core/src/main/java/org/jbake/launcher/Baker.java deleted file mode 100644 index 536625c98..000000000 --- a/jbake-core/src/main/java/org/jbake/launcher/Baker.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.jbake.launcher; - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.jbake.app.JBakeException; -import org.jbake.app.Oven; -import org.jbake.app.configuration.JBakeConfiguration; -import org.jbake.app.configuration.JBakeConfigurationFactory; - -import java.text.MessageFormat; -import java.util.List; - -/** - * Delegate class responsible for launching a Bake. - * - * @author jmcgarr@gmail.com - */ -public class Baker { - - /** - * @param options The given cli options - * @param config The project configuration - * @deprecated use {@link Baker#bake(JBakeConfiguration)} instead - */ - @Deprecated - public void bake(final LaunchOptions options, final CompositeConfiguration config) { - JBakeConfiguration configuration = new JBakeConfigurationFactory().createDefaultJbakeConfiguration(options.getSource(), options.getDestination(), config, options.isClearCache()); - bake(configuration); - } - - public void bake(final JBakeConfiguration config) { - final Oven oven = new Oven(config); - oven.bake(); - - final List errors = oven.getErrors(); - if (!errors.isEmpty()) { - final StringBuilder msg = new StringBuilder(); - // TODO: decide, if we want the all errors here - msg.append( MessageFormat.format("JBake failed with {0} errors:\n", errors.size())); - int errNr = 1; - for (final Throwable error : errors) { - msg.append(MessageFormat.format("{0}. {1}\n", errNr, error.getMessage())); - ++errNr; - } - throw new JBakeException(SystemExit.ERROR ,msg.toString(), errors.get(0)); - } - } -} diff --git a/jbake-core/src/main/java/org/jbake/launcher/CustomFSChangeListener.java b/jbake-core/src/main/java/org/jbake/launcher/CustomFSChangeListener.java deleted file mode 100644 index 3c6006177..000000000 --- a/jbake-core/src/main/java/org/jbake/launcher/CustomFSChangeListener.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.jbake.launcher; - -import org.apache.commons.vfs2.FileChangeEvent; -import org.apache.commons.vfs2.FileListener; -import org.apache.commons.vfs2.FileObject; -import org.jbake.app.Oven; -import org.jbake.app.configuration.JBakeConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - -public class CustomFSChangeListener implements FileListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(CustomFSChangeListener.class); - - private final JBakeConfiguration config; - - public CustomFSChangeListener(JBakeConfiguration config) { - this.config = config; - } - - @Override - public void fileCreated(FileChangeEvent event) throws Exception { - LOGGER.info("File created event detected: {}", event.getFileObject().getURL()); - exec(event.getFileObject()); - } - - @Override - public void fileDeleted(FileChangeEvent event) throws Exception { - LOGGER.info("File deleted event detected: {}", event.getFileObject().getURL()); - exec(event.getFileObject()); - } - - @Override - public void fileChanged(FileChangeEvent event) throws Exception { - LOGGER.info("File changed event detected: {}", event.getFileObject().getURL()); - exec(event.getFileObject()); - } - - private void exec(FileObject file) { - final Oven oven = new Oven(config); - oven.bake(new File(file.getName().getPath())); - } -} diff --git a/jbake-core/src/main/java/org/jbake/launcher/Init.java b/jbake-core/src/main/java/org/jbake/launcher/Init.java deleted file mode 100644 index ae1dd1bca..000000000 --- a/jbake-core/src/main/java/org/jbake/launcher/Init.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.jbake.launcher; - -import org.apache.commons.configuration2.CompositeConfiguration; -import org.jbake.app.ZipUtil; -import org.jbake.app.configuration.DefaultJBakeConfiguration; -import org.jbake.app.configuration.JBakeConfiguration; - -import java.io.File; -import java.io.FileInputStream; - -/** - * Initialises sample folder structure with pre-defined template - * - * @author Jonathan Bullock jonbullock@gmail.com - * - */ -public class Init { - - private final JBakeConfiguration config; - - /** - * @param config The project configuration - * @deprecated use {@link Init#Init(JBakeConfiguration)} instead - */ - @Deprecated - public Init(CompositeConfiguration config) { - this(new DefaultJBakeConfiguration(config)); - } - - public Init(JBakeConfiguration config) { - this.config = config; - } - - /** - * Performs checks on output folder before extracting template file - * - * @param outputFolder Target directory for extracting template file - * @param templateLocationFolder Source location for template file - * @param templateType Type of the template to be used - * @throws Exception if required folder structure can't be achieved without content overwriting - */ - public void run(File outputFolder, File templateLocationFolder, String templateType) throws Exception { - if (!outputFolder.canWrite()) { - throw new Exception("Output folder is not writeable!"); - } - - File[] contents = outputFolder.listFiles(); - boolean safe = true; - if (contents != null) { - for (File content : contents) { - if (content.isDirectory()) { - if (content.getName().equalsIgnoreCase(config.getTemplateFolderName())) { - safe = false; - } - if (content.getName().equalsIgnoreCase(config.getContentFolderName())) { - safe = false; - } - if (content.getName().equalsIgnoreCase(config.getAssetFolderName())) { - safe = false; - } - } - } - } - - if (!safe) { - throw new Exception(String.format("Output folder '%s' already contains structure!", - outputFolder.getAbsolutePath())); - } - if (config.getExampleProjectByType(templateType) != null) { - File templateFile = new File(templateLocationFolder, config.getExampleProjectByType(templateType)); - if (!templateFile.exists()) { - throw new Exception("Cannot find example project file: " + templateFile.getPath()); - } - ZipUtil.extract(new FileInputStream(templateFile), outputFolder); - } else { - throw new Exception("Cannot locate example project type: " + templateType); - } - } -} diff --git a/jbake-core/src/main/java/org/jbake/launcher/JettyServer.java b/jbake-core/src/main/java/org/jbake/launcher/JettyServer.java deleted file mode 100644 index cff105932..000000000 --- a/jbake-core/src/main/java/org/jbake/launcher/JettyServer.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.jbake.launcher; - -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerList; -import org.eclipse.jetty.server.handler.ResourceHandler; -import org.jbake.app.JBakeException; -import org.jbake.app.configuration.JBakeConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Closeable; -import java.io.IOException; - - -/** - * Provides Jetty server related functions - * - * @author Jonathan Bullock jonbullock@gmail.com - */ -public class JettyServer implements Closeable { - private static final Logger LOGGER = LoggerFactory.getLogger(JettyServer.class); - - private Server server; - - @Deprecated - public void run(String resourceBase, String port) { - LOGGER.warn("DEPRECATED. This method will be removed in the next major release. Use run(String resourceBase, JBakeConfiguration config) instead."); - run(resourceBase, "/", "localhost", Integer.parseInt(port)); - } - - public void run(String resourceBase, JBakeConfiguration configuration) { - run(resourceBase, configuration.getServerContextPath(), configuration.getServerHostname(), configuration.getServerPort()); - } - - /** - * Run Jetty web server serving out supplied path on supplied port - * - * @param resourceBase Base directory for resources to be served - * @param port Required server port - */ - private void run(String resourceBase, String contextPath, String hostname, int port) { - try { - server = new Server(); - ServerConnector connector = new ServerConnector(server); - connector.setHost(hostname); - connector.setPort(port); - server.addConnector(connector); - - ResourceHandler resource_handler = new ResourceHandler(); - resource_handler.setDirectoriesListed(true); - resource_handler.setWelcomeFiles(new String[]{"index", "index.html"}); - resource_handler.setResourceBase(resourceBase); - - ContextHandler contextHandler = new ContextHandler(); - contextHandler.setContextPath(contextPath); - contextHandler.setHandler(resource_handler); - - HandlerList handlers = new HandlerList(); - - handlers.setHandlers(new Handler[]{contextHandler, new DefaultHandler()}); - server.setHandler(handlers); - - LOGGER.info("Serving out contents of: [{}] on http://{}:{}{}", resourceBase, hostname, port, contextHandler.getContextPath()); - LOGGER.info("(To stop server hit CTRL-C)"); - - server.start(); - server.join(); - } catch (Exception e) { - throw new JBakeException(SystemExit.SERVER_ERROR, "unable to start the server", e); - } - } - - public boolean isStarted() { - return server != null && server.isStarted(); - } - - @Override - public void close() throws IOException { - if (server.isRunning()) { - try { - server.stop(); - } catch (Exception e) { - LOGGER.error("unable to stop server"); - } - } - - } -} diff --git a/jbake-core/src/main/java/org/jbake/launcher/LaunchOptions.java b/jbake-core/src/main/java/org/jbake/launcher/LaunchOptions.java deleted file mode 100644 index c4bd9cfc2..000000000 --- a/jbake-core/src/main/java/org/jbake/launcher/LaunchOptions.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.jbake.launcher; - -import org.jbake.app.configuration.ConfigUtil; -import picocli.CommandLine.ArgGroup; -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; -import picocli.CommandLine.Parameters; - -import java.io.File; - -@Command( - description = "JBake is a Java based, open source, static site/blog generator for developers & designers", - name = "jbake", - usageHelpAutoWidth = true -) -public class LaunchOptions { - @Parameters(index = "0", description = "source folder of site content (with templates and assets), if not supplied will default to current directory", arity = "0..1") - private String source; - - @Parameters(index = "1", description = "destination folder for output, if not supplied will default to a folder called \"output\" in the current directory", arity = "0..1") - private String destination; - - @Option(names = {"-b", "--bake"}, description = "performs a bake") - private boolean bake; - - @ArgGroup(exclusive = false, heading = "%n%nJBake initialization%n%n") - private InitOptions initGroup; - - @Option(names = {"-s", "--server"}, description = "runs HTTP server to serve out baked site, if no is supplied will default to a folder called \"output\" in the current directory") - private boolean runServer; - - @Option(names = {"-h", "--help"}, description = "prints this message", usageHelp = true) - private boolean helpRequested; - - @Option(names = {"--reset"}, description = "clears the local cache, enforcing rendering from scratch") - private boolean clearCache; - - @Option(names = {"-c", "--config"}, description = "use specified file for configuration (defaults to " + ConfigUtil.CONFIG_FILE +" in the source folder if not supplied)") - private String config; - - @Option(names = {"--prop-encoding"}, description = "use given encoding to load properties file. default: utf-8") - private String propertiesEncoding = "utf-8"; - - @Option(names = {"-ls", "--list-settings"}, description = "list configuration settings") - private boolean listConfig; - - public String getTemplate() { - return initGroup.template; - } - - public File getSource() { - if (source != null) { - return new File(source); - } else { - return new File(System.getProperty("user.dir")); - } - } - - public String getSourceValue() { - return source; - } - - public File getDestination() { - if (destination != null) { - return new File(destination); - } else { - return new File(getSource(), "output"); - } - } - - public String getDestinationValue() { - return destination; - } - - public File getConfig() { - if (config != null) { - return new File(config); - } else { - return new File(getSource(), "jbake.properties"); - } - } - - public String getConfigValue() { - return config; - } - - public boolean isHelpNeeded() { - return helpRequested || !(isListConfig() || isBake() || isRunServer() || isInit() || source != null || destination != null); - } - - public boolean isRunServer() { - return runServer; - } - - public boolean isInit() { - return (initGroup != null && initGroup.init); - } - - public boolean isClearCache() { - return clearCache; - } - - public boolean isBake() { - return bake || (source != null && destination != null); - } - - public boolean isListConfig() { - return listConfig; - } - - public String getPropertiesEncoding() { - return propertiesEncoding; - } - - static class InitOptions { - - @Option(names = {"-i", "--init"}, paramLabel = "", description = "initialises required folder structure with default templates (defaults to current directory if is not supplied)", required = true) - private boolean init; - - @Option(names = {"-t", "--template"}, defaultValue = "freemarker", fallbackValue = "freemarker", description = "use specified template engine for default templates (uses Freemarker if