[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "buy_me_a_coffee: erbussierel\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Community Support\n    url: https://github.com/PebbleTemplates/pebble/discussions\n    about: Please ask and answer questions here."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue.md",
    "content": "---\nname: General\nabout: Bugs, enhancements, documentation, tasks.\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n<!--\nThanks for raising an issue. Please take the time to review the following\ncategories as some of them do not apply here.\n\n🙅 \"Please DO NOT Raise an Issue\" Cases\n- Question\nSTOP!! Please ask questions about how to use something, or to understand why something isn't\nworking as you expect it. Please use [Discussion](https://github.com/PebbleTemplates/pebble/discussions)\n- Security Vulnerability\nSTOP!! Please don't raise security vulnerabilities here. Head over to https://github.com/PebbleTemplates/pebble/security to learn how to disclose them responsibly.\n\n🐞 Bug report\nPlease provide details of the problem, including the version of Pebble that you\nare using. If possible, please provide a test case or sample application that reproduces\nthe problem. This makes it much easier for us to diagnose the problem and to verify that\nwe have fixed it.\n\n🎁 Enhancement\nPlease start by describing the problem that you are trying to solve. There may already\nbe a solution, or there may be a way to solve it that you hadn't considered.\n\nTIP: You can always edit your issue if it isn't formatted correctly.\n     See https://guides.github.com/features/mastering-markdown \n-->"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"maven\"\n    directories:\n      - \"/pebble\"\n      - \"/pebble-spring/pebble-spring7\"\n      - \"/pebble-spring/pebble-spring-boot-starter\"\n    schedule:\n      interval: \"daily\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    ignore:\n      - dependency-name: \"javax.servlet:servlet-api\"\n        update-types: [\"version-update:semver-major\", \"version-update:semver-minor\", \"version-update:semver-patch\"]\n      - dependency-name: \"jakarta.servlet:jakarta.servlet-api\"\n        update-types: [\"version-update:semver-major\", \"version-update:semver-minor\", \"version-update:semver-patch\"]\n    groups:\n      all-dependencies:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"maven\"\n    directories:\n      - \"/pebble-spring/pebble-spring6\"\n      - \"/pebble-spring/pebble-legacy-spring-boot-starter\"\n    schedule:\n      interval: \"daily\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    ignore:\n      - dependency-name: \"jakarta.servlet:jakarta.servlet-api\"\n        update-types: [\"version-update:semver-major\", \"version-update:semver-minor\", \"version-update:semver-patch\"]\n      - dependency-name: \"*\"\n        update-types: [\"version-update:semver-major\"]\n    groups:\n      all-dependencies:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/maven.yml",
    "content": "# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven\n\n# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n\nname: Build\n\non:\n  push:\n    branches: [ \"*\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        java-version: [ 17, 21, 25 ]\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up JDK\n      uses: actions/setup-java@v5\n      with:\n        java-version: ${{ matrix.java-version }}\n        distribution: 'temurin'\n        cache: maven\n    - name: Build with Maven\n      run: mvn -B package --file pom.xml\n\n    # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive\n    #- name: Update dependency graph\n    #  uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6\n"
  },
  {
    "path": ".gitignore",
    "content": "*.class\n\n# Package Files #\n*.jar\n*.war\n*.ear\n\n# Mac OS X\n.DS_Store\n\n# Eclipse #\n.project\n.classpath\n.settings/\n\n# IntelliJ\n.idea/\n*.iml\n*.ipr\n*.iws\n\ntarget/\n/target\n/bin/\n\n.gradle/\nbuild/\n\n*.releaseBackup\nrelease.properties\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2013, Mitchell Bösecke\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice, this\n  list of conditions and the following disclaimer in the documentation and/or\n  other materials provided with the distribution.\n\n* Neither the name of the {organization} nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# Pebble [![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/erbussierel) [![Build](https://github.com/PebbleTemplates/pebble/actions/workflows/maven.yml/badge.svg)](https://github.com/PebbleTemplates/pebble/actions/workflows/maven.yml)\n\nPebble is a java templating engine inspired by [Twig](https://twig.symfony.com/). It separates itself from the crowd\nwith its inheritance feature and its easy-to-read syntax. It ships with built-in autoescaping for security, and it\nincludes integrated support for internationalization.\n\nFor more information please visit the [website](https://pebbletemplates.io).\n\n# Breaking changes in version 4.1.x\n\n- If you do not provide a custom Loader, Pebble will now use only a `ClasspathLoader` by default, same as the spring autoconfiguration.\n  Before that, it would have used an instance of the `DelegatingLoader` which consists of a `ClasspathLoader` and a `FileLoader` behind the scenes to find your templates.\n- Modify the `FileLoader` to use a mandatory base directory parameter.\n\n# Breaking changes in version 4.0.x\n\n- Use one of the following artifactId according to the spring boot version that you are using\n\n| ArtifactId                        | spring-boot version |\n|-----------------------------------|---------------------|\n| pebble-legacy-spring-boot-starter | 3.x.x               |\n| pebble-spring-boot-starter        | 4.x.x               |\n\n- The following spring boot properties has been moved to `.servlet` or `.reactive`\n\n| Old property                    | New Property                            |\n|---------------------------------|-----------------------------------------|\n| pebble.allowRequestOverride     | pebble.servlet.allowRequestOverride     |\n| pebble.allowSessionOverride     | pebble.servlet.allowSessionOverride     |\n| pebble.cache                    | pebble.servlet.cache                    |\n| pebble.contentType              | pebble.servlet.contentType              |\n| pebble.exposeRequestAttributes  | pebble.servlet.exposeRequestAttributes  |\n| pebble.exposeSessionAttributes  | pebble.servlet.exposeSessionAttributes  |\n| pebble.exposeSpringMacroHelpers | pebble.servlet.exposeSpringMacroHelpers |\n|                                 | pebble.reactive.mediaTypes              |\n\nFor more information, please consult the spring-boot integration documentation in\nthe [Boot externalized configuration section](https://pebbletemplates.io/wiki/guide/spring-boot-integration/)\n\n# Breaking changes in version 3.2.x\n\n- Rename package from `com.mitchellbosecke` to `io.pebbletemplates`\n- Change default suffix to `.peb` instead of `.pebble` in spring boot autoconfiguration\n- Rename method `getInstance` to `createInstance` in `BinaryOperator` interface (#521)\n\n## License\n\n    Copyright (c) 2013, Mitchell Bösecke\n    All rights reserved.\n    \n    Redistribution and use in source and binary forms, with or without modification,\n    are permitted provided that the following conditions are met:\n    \n    * Redistributions of source code must retain the above copyright notice, this\n      list of conditions and the following disclaimer.\n    \n    * Redistributions in binary form must reproduce the above copyright notice, this\n      list of conditions and the following disclaimer in the documentation and/or\n      other materials provided with the distribution.\n    \n    * Neither the name of the {organization} nor the names of its\n      contributors may be used to endorse or promote products derived from\n      this software without specific prior written permission.\n    \n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\n    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nIf you think you have found a security vulnerability, please ***DO NOT*** disclose it publicly until we've had a chance to fix it. Please don't report security vulnerabilities using GitHub issues, instead just click on `Report a vulnerability` button on this page to disclose them responsibly. \n"
  },
  {
    "path": "docs/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>io.pebbletemplates</groupId>\n    <artifactId>pebble-project</artifactId>\n    <version>4.1.2-SNAPSHOT</version>\n  </parent>\n\n  <artifactId>docs</artifactId>\n  <packaging>pom</packaging>\n\n  <name>Pebble docs</name>\n  <description>Pebble documentation</description>\n  <url>http://pebbletemplates.io</url>\n\n  <properties>\n    <orchid.version>0.17.4</orchid.version>\n  </properties>\n\n  <profiles>\n    <profile>\n      <id>doc</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>io.github.javaeden.orchid</groupId>\n            <artifactId>orchid-maven-plugin</artifactId>\n            <version>${orchid.version}</version>\n\n            <!-- 1. Include all official Orchid plugins and themes -->\n            <dependencies>\n              <dependency>\n                <groupId>io.github.javaeden.orchid</groupId>\n                <artifactId>OrchidDocs</artifactId>\n                <version>${orchid.version}</version>\n              </dependency>\n              <dependency>\n                <groupId>io.github.javaeden.orchid</groupId>\n                <artifactId>OrchidPluginDocs</artifactId>\n                <version>${orchid.version}</version>\n              </dependency>\n              <dependency>\n                <groupId>io.github.javaeden.orchid</groupId>\n                <artifactId>OrchidGithub</artifactId>\n                <version>${orchid.version}</version>\n              </dependency>\n            </dependencies>\n\n            <!-- 3. Use the 'BsDoc' theme, and view the site locally at 'http://localhost:8080' -->\n            <configuration>\n              <theme>Editorial</theme>\n              <version>${project.version}</version>\n              <baseUrl>https://pebbletemplates.io</baseUrl>\n            </configuration>\n\n            <executions>\n              <execution>\n                <phase>verify</phase>\n                <goals>\n                  <goal>deploy</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n\n  <!-- 2. Get Orchid from Jcenter, Bintray, and Jitpack -->\n  <pluginRepositories>\n    <pluginRepository>\n      <id>jcenter</id>\n      <url>https://jcenter.bintray.com </url>\n    </pluginRepository>\n    <pluginRepository>\n      <id>kotlinx</id>\n      <url>https://kotlin.bintray.com/kotlinx</url>\n    </pluginRepository>\n  </pluginRepositories>\n</project>\n"
  },
  {
    "path": "docs/src/orchid/resources/assets/css/github-fork-ribbon.css",
    "content": "/*!\n * \"Fork me on GitHub\" CSS ribbon v0.2.2 | MIT License\n * https://github.com/simonwhitaker/github-fork-ribbon-css\n*/\n\n.github-fork-ribbon {\n    width: 12.1em;\n    height: 12.1em;\n    position: absolute;\n    overflow: hidden;\n    top: 0;\n    right: 0;\n    z-index: 9999;\n    pointer-events: none;\n    font-size: 13px;\n    text-decoration: none;\n    text-indent: -999999px;\n    border-bottom: none;\n}\n\n.github-fork-ribbon.fixed {\n    position: fixed;\n}\n\n.github-fork-ribbon:hover, .github-fork-ribbon:active {\n    background-color: rgba(0, 0, 0, 0.0);\n}\n\n.github-fork-ribbon:before, .github-fork-ribbon:after {\n    /* The right and left classes determine the side we attach our banner to */\n    position: absolute;\n    display: block;\n    width: 15.38em;\n    height: 1.54em;\n\n    top: 3.23em;\n    right: -3.23em;\n\n    -webkit-box-sizing: content-box;\n    -moz-box-sizing: content-box;\n    box-sizing: content-box;\n\n    -webkit-transform: rotate(45deg);\n    -moz-transform: rotate(45deg);\n    -ms-transform: rotate(45deg);\n    -o-transform: rotate(45deg);\n    transform: rotate(45deg);\n}\n\n.github-fork-ribbon:before {\n    content: \"\";\n\n    /* Add a bit of padding to give some substance outside the \"stitching\" */\n    padding: .38em 0;\n\n    /* Set the base colour */\n    background-color: #a00;\n\n    /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */\n    background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15)));\n    background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));\n    background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));\n    background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));\n    background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));\n    background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));\n\n    /* Add a drop shadow */\n    -webkit-box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);\n    -moz-box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);\n    box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);\n\n    pointer-events: auto;\n}\n\n.github-fork-ribbon:after {\n    /* Set the text from the data-ribbon attribute */\n    content: attr(data-ribbon);\n\n    /* Set the text properties */\n    color: #fff;\n    font: 700 1em \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n    line-height: 1.54em;\n    text-decoration: none;\n    text-shadow: 0 -.08em rgba(0, 0, 0, 0.5);\n    text-align: center;\n    text-indent: 0;\n\n    /* Set the layout properties */\n    padding: .15em 0;\n    margin: .15em 0;\n\n    /* Add \"stitching\" effect */\n    border-width: .08em 0;\n    border-style: dotted;\n    border-color: #fff;\n    border-color: rgba(255, 255, 255, 0.7);\n}\n\n.github-fork-ribbon.left-top, .github-fork-ribbon.left-bottom {\n    right: auto;\n    left: 0;\n}\n\n.github-fork-ribbon.left-bottom, .github-fork-ribbon.right-bottom {\n    top: auto;\n    bottom: 0;\n}\n\n.github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after {\n    right: auto;\n    left: -3.23em;\n}\n\n.github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after {\n    top: auto;\n    bottom: 3.23em;\n}\n\n.github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after {\n    -webkit-transform: rotate(-45deg);\n    -moz-transform: rotate(-45deg);\n    -ms-transform: rotate(-45deg);\n    -o-transform: rotate(-45deg);\n    transform: rotate(-45deg);\n}"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_0.md",
    "content": "---\nversion: '3.0.0'\n---\n\n- Java 8\n- Macros have access to all variables within the template and are no longer restricted to a \"local scope\"\n- New signature for filters/functions/tests that accepts the PebbleTemplate, EvaluationContext, and line number\n- Converted EvaluationContext to an interface with limited access to the underlying implementation\n- A \"null\" used as a boolean expression now throws an error if strict variables is true, treated as \"false\" otherwise\n- Improved error reporting\n- Java8 Date API support on date filter\n- Replace guava cache with caffeine\n- Add String Interpolation (#235)\n- Add the possibility to extend attribute access (#258)\n- Remove DynamicAttributeProvider\n- Render content on demand. Expose getLiteralTemplate(String templateName) (#295)\n- Introduce SpecializedWriter, (#358)\n- Many performance enhancements\n- Method resolution with overloaded method signatures (#367)\n- Use AbstractTemplateViewResolverProperties for spring-boot (#369)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_1.md",
    "content": "---\nversion: '3.0.1'\n---\n\n- Macros are restricted to a local scope (#371)\n- Macros have access to global attributes via \"_context\" variable (#242)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_10.md",
    "content": "---\nversion: '3.0.10'\n---\n\n- Add support for JDK 9++ (#442, #443)\n- Add macro support for errors(BindingResult) in spring extension\n- Add spring-petclinic example \n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_2.md",
    "content": "---\nversion: '3.0.2'\n---\n\n- Add PebbleCache interface and use ConcurrentMap instead of Caffeine as default template/tag cache (#381)\n- Use unbescape instead of Coverity Escapers (#380)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_3.md",
    "content": "---\nversion: '3.0.3'\n---\n\n- Support more expressions for if operator (#387)\n- Consider adding greedyMatchMethod to PebbleProperties (#388)\n- Use locale from context for lower/upper filter (#390)\n- Restore cache:invalidateAll method (#393)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_4.md",
    "content": "---\nversion: '3.0.4'\n---\n\n- Make NOT operator more tolerant to argument type (#394)\n- Make ternary if operator more tolerant to argument type (#399)\n- Adjust AndExpression and OrExpression logic (#398)\n- Add JSON escaping as part of the default escaping strategies (#395)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_5.md",
    "content": "---\nversion: '3.0.5'\n---\n\n- Cannot access List methods (#402)\n- Implement to boolean smart-casting in OR and AND operators (#405)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_6.md",
    "content": "---\nversion: '3.0.6'\n---\n\n- Check that parseNewVariableName() parses a valid identifier (#409) \n- Set up Pebble documentation site using Orchid  (#411)\n- Add twig compatibility matrix documentation (#247)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_7.md",
    "content": "---\nversion: '3.0.7'\n---\n\n- Add Automatic-Module-Name to support Java 9 modules (#416) \n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_8.md",
    "content": "---\nversion: '3.0.8'\n---\n\n- Add split filter (#421) \n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_0_9.md",
    "content": "---\nversion: '3.0.9'\n---\n\n- Add embed tag (#319, #318, #224, #378, #434)\n- Offline documentation (#282, #432)\n- Make allowGetClass check case insensitive (#435, #436) \n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_1_0.md",
    "content": "---\nversion: '3.1.0'\n---\n\n- **BREAKING CHANGES**: Consider avoiding feature version in spring boot starter name (#459)\n- Upgrade spring boot integration to Spring Boot 2.1 (#460)\n- Allow override core operators (#455, #456)\n- Allows variables to be set in templates when provided context is an immutable map (#453)\n- Fixed a few typos in the docs (#452)\n- Remove unnecessary variable 'state' in LexerImpl by using Stack (#450)\n- Updates Orchid to 0.17.1 and fixes its breaking changes (#448)\n- Null value in a Map should not throw AttributeNotFoundException (#447, #446)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_1_1.md",
    "content": "---\nversion: '3.1.1'\n---\n\n- Use a list of unsafeMethods and rename allowGetClass to allowUnsafeMethods (#454)\n- PebbleTemplateAvailabilityProvider doesn't find template files from the classpath (#464)\n- Added inline verbatim description to Verbatim tag documentation (#422)\n- Fix issue of embed tag changing cached templates (#475)\n- Fixes bug which allowed sequential String literals to be parsed correctly (#482)\n- Update to spring boot 2.2 (486)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_1_2.md",
    "content": "---\nversion: '3.1.2'\n---\n\n- Add support for jdk13 (#487)\n- Add basic support for Spring 5 WebFlux reactive in Spring Boot 2 (#333) \n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_1_3.md",
    "content": "---\nversion: '3.1.3'\n---\n\n- Allow treating all number literals as BigDecimals to avoid NumberFormatExceptions (#503)\n- Custom operator can't use external context (#497) \n- Allow all variable names to be retrieve from ScopeChain/Scope (#291)\n- Empty map in strict mode does not throw exception (#491)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_1_4.md",
    "content": "---\nversion: '3.1.4'\n---\n\n- Slice filter: Use collection size when toIndex is greater than collection size  (#504)\n- Adjust spring boot doc (#509) \n- Build with jdk14 (#508)\n- Set proxyBeanMethods to false and build with spring boot 2.3 (#507)\n- Add access to Spring Beans/request/session and response when using Pebble with WebFlux (#512)\n- Remove allowUnsafeMethods property and replace it with methodAccessValidator. Default one is BlacklistMethodAccessValidtor (#511)  "
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_1_5.md",
    "content": "---\nversion: '3.1.5'\n---\n\n- Add timeZone parameter to date filter  (#530)\n- Add setting to limit the size of output when evaluating a template  (#529)\n- Fix variable name starting with operator  (#541)\n- Added Servlet5Loader to support Servlet 5.0 (#549)\n- Throw an error when an import alias references an invalid macro (#559)\n- Special for loop variables class, to support all attribute resolvers (#560)\n- Support all Java identifiers (#544)\n- Provide option to customize stock extensions (#552)\n- Cannot use sort filter right after split filter (#568)\n- Support array of List in sort filter  (#569)\n- Extension global variables in macros (#571)\n- Add base64 and sha256 filters (#574)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_1_6.md",
    "content": "---\nversion: '3.1.6'\n---\n\n- Make CacheKey class extendible (#604)\n- Remove support for spring 1.5.x (#628)\n- Fix for older Android versions (#585 #581 #580)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_2_0.md",
    "content": "---\nversion: '3.2.0'\n---\n\n- Add support for spring framework 6 and spring-boot 3 (#630)\n- Bump minimum supported java version to 17 in pebble-spring6 and pebble-spring-boot-starter in order to work with\n  spring (#630)\n- Add a memory loader that supports inheritance and doesn't require a filesystem. This is useful for applications\n  that retrieve templates from a database for example (#617).\n- **BREAKING CHANGE**: Change default suffix to `.peb` instead of `.pebble` in spring boot autoconfiguration (#553)\n- **BREAKING CHANGE**: Rename method `getInstance` to `createInstance` in `BinaryOperator` interface (#521)\n- **BREAKING CHANGE**: Rename package from `com.mitchellbosecke` to `io.pebbletemplates` (#635)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_2_1.md",
    "content": "---\nversion: '3.2.1'\n---\n\n- Fix the null pointer exception on the replace filter (#650)\n- Add native hints for GraalVM (#648)"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_2_2.md",
    "content": "---\nversion: '3.2.2'\n---\n\n- Throw a more detailed exception from DefaultAttributeResolver (#653)\n- Make expression nodes fully visitable (#657)\n- Added getTemplates function to MemoryLoader (#660)\n- Generate OSGi metadata into pebble jar to make it OSGi compliant (#663)\n- Add flag for parameter name retention in maven compiler plugin (#664)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_2_3.md",
    "content": "---\nversion: '3.2.3'\n---\n\n- Dynamically open different feature according to different scenarios (#671)\n- Fix the ability to provide your own PebbleViewResolver (#686)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v3_2_4.md",
    "content": "---\nversion: '3.2.4'\n---\n\n- Fix the problem of unit test failure under JDK 21 (#692)\n- Fix the problem of not resolving reactive values in models  (#691)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v4_0_0.md",
    "content": "---\nversion: '4.0.0'\n---\n\n# BREAKING CHANGES\n- Add support for spring boot 4 (#704)\n- Use one of the following artifactId according to the spring boot version that you are using\n\n| ArtifactId                        | spring-boot version |\n|-----------------------------------|---------------------|\n| pebble-legacy-spring-boot-starter | 3.x.x               |\n| pebble-spring-boot-starter        | 4.x.x               |\n\n- The following spring boot properties has been moved to `.servlet` or `.reactive`\n\n| Old property                    | New Property                            |\n|---------------------------------|-----------------------------------------|\n| pebble.allowRequestOverride     | pebble.servlet.allowRequestOverride     |\n| pebble.allowSessionOverride     | pebble.servlet.allowSessionOverride     |\n| pebble.cache                    | pebble.servlet.cache                    |\n| pebble.contentType              | pebble.servlet.contentType              |\n| pebble.exposeRequestAttributes  | pebble.servlet.exposeRequestAttributes  |\n| pebble.exposeSessionAttributes  | pebble.servlet.exposeSessionAttributes  |\n| pebble.exposeSpringMacroHelpers | pebble.servlet.exposeSpringMacroHelpers |\n|                                 | pebble.reactive.mediaTypes              |\n\nFor more information, please consult the spring-boot integration documentation in\nthe [Boot externalized configuration section](https://pebbletemplates.io/wiki/guide/spring-boot-integration/)\n\n# New Features\n- Add nl2br filter (#699)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v4_1_0.md",
    "content": "---\nversion: '4.1.0'\n---\n\n# BREAKING CHANGES\n- If you do not provide a custom Loader, Pebble will now use only a `ClasspathLoader` by default, same as the spring autoconfiguration.\n  Before that, it would have used an instance of the `DelegatingLoader` which consists of a `ClasspathLoader` and a `FileLoader` behind the scenes to find your templates.\n- Modify the `FileLoader` to use a mandatory base directory parameter.\n\n# Security\n- Fix [CVE-2025-1686](https://nvd.nist.gov/vuln/detail/CVE-2025-1686) (#715)\n\n# New Features\n- Use a default existing format of `yyyy-MM-dd'T'HH:mm:ssZ` when using date filter with a string (#677)\n- Look for exact method / field match when doing reflection. Look for method get/is/has if none match (#712)\n- Support dynamic named arguments (#741)\n\n# Bug Fixes\n- NaN must return false instead of throwing an exception (#695)\n\n# Dependency Upgrades\n- Upgrade to SLF4J 2.0.17 (#709)\n"
  },
  {
    "path": "docs/src/orchid/resources/changelog/v4_1_1.md",
    "content": "---\nversion: '4.1.1'\n---\n\n# New Features\n- Expose AST Root Node to Enable Custom NodeVisitor Implementation (#701)\n\n# Bug Fixes\n- Catch all exceptions in DelegatingLoader (#765)\n"
  },
  {
    "path": "docs/src/orchid/resources/config.yml",
    "content": "site:\n  about:\n    siteName: 'Pebble Templates'\n    subtitle: 'A lightweight but rock solid Java templating engine.'\n\ntheme:\n  primaryColor: '#72A6D0'\n  menu:\n    - type: 'separator'\n      title: 'About'\n    - type: 'page'\n      title: 'Home'\n      itemId: 'Home'\n    - type: 'page'\n      itemId: 'Spring Boot Integration'\n    - type: 'link'\n      title: 'Spring petclinic example'\n      url: 'https://github.com/PebbleTemplates/spring-petclinic'\n    - type: 'page'\n      itemId: 'Twig Compatibility'\n    - type: 'page'\n      itemId: 'Contributing'\n    - type: 'page'\n      itemId: 'Changelog'\n    - type: 'link'\n      title: 'Offline Documentation (PDF)'\n      url: '#{$0|baseUrl}/wiki/book.pdf'\n    - type: 'link'\n      title: 'Buy me a coffee'\n      url: 'https://buymeacoffee.com/erbussierel'\n    - type: 'separator'\n      title: 'Wiki'\n    - type: 'wiki'\n    - type: 'separator'\n      title: 'API Docs'\n    - type: 'page'\n      title: 'Javadocs'\n      itemId: 'io.pebbletemplates'\n  extraCss:\n    - 'assets/css/pygments.scss'\n    - 'assets/css/orchidJavadoc.scss'\n    - 'assets/css/github-fork-ribbon.css'\n\nservices:\n  publications:\n    stages:\n      - type: githubPages\n        branch: gh-pages\n        publishType: CleanBranch\n        username: PebbleTemplates\n        repo: pebble\n      - type: githubReleases\n        repo: 'PebbleTemplates/pebble'\n\nallPages:\n  components:\n    - type: 'pageContent'\n    - type: 'prism'\n      languages:\n        - 'java'\n        - 'twig'\n        - 'markup'\n\nwiki:\n  defaultConfig:\n    includeIndexInPageTitle: false\n    createPdf: true\n\njavadoc:\n  sourceDirs:\n    - '../../../../pebble/src/main/java'\n  pages:\n    menu:\n      - type: 'javadocPackages'\n      - type: 'javadocClasses'"
  },
  {
    "path": "docs/src/orchid/resources/data/contributors.yml",
    "content": "- name: Mitchell Bösecke\n  link: https://github.com/mbosecke\n- name: Eric Bussieres\n  link: https://github.com/ebussieres\n- name: Héctor López\n  link: https://github.com/hectorlf\n- name: Djalma Olivera\n  link: https://github.com/djalmaoliveira\n- name: Vladimir Loshchin\n  link: https://github.com/badbob\n- name: yanxiyue\n  link: https://github.com/yanxiyue\n- name: Vladimir Danilov\n  link: https://github.com/diasonti\n- name: Casey Brooks\n  link: https://github.com/cjbrooks12\n- name: Bastien Jansen\n  link: https://github.com/bjansen"
  },
  {
    "path": "docs/src/orchid/resources/data/twig-compatibility/filters.yml",
    "content": "- name: 'abs'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/abs.html'\n  support: 'full'\n  page: 'abs'\n- name: 'batch'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/batch.html'\n  support: 'none'\n- name: 'capitalize'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/capitalize.html'\n  support: 'full'\n  page: 'capitalize'\n- name: 'convert_encoding'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/convert_encoding.html'\n  support: 'none'\n- name: 'date'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/date.html'\n  support: 'partial'\n  page: 'date'\n  notes: 'This filter uses `java.util.Date` date formatting instead of PHP-style formatting.'\n- name: 'date_modify'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/date_modify.html'\n  support: 'none'\n- name: 'default'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/default.html'\n  support: 'full'\n  page: 'default'\n- name: 'escape'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/escape.html'\n  support: 'full'\n  page: 'escape'\n- name: 'first'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/first.html'\n  support: 'full'\n  page: 'first'\n- name: 'format'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/format.html'\n  support: 'full'\n  page: 'format'\n- name: 'join'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/join.html'\n  support: 'none'\n  page: 'join'\n- name: 'json_encode'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/json_encode.html'\n  support: 'none'\n- name: 'keys'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/keys.html'\n  support: 'none'\n- name: 'last'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/last.html'\n  support: 'full'\n  page: 'last'\n- name: 'length'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/length.html'\n  support: 'full'\n  page: 'length'\n- name: 'lower'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/lower.html'\n  support: 'full'\n  page: 'lower'\n- name: 'merge'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/merge.html'\n  support: 'none'\n- name: 'nl2br'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/nl2br.html'\n  support: 'full'\n  page: 'nl2br'\n- name: 'number_format'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/number_format.html'\n  support: 'partial'\n  page: 'numberformat'\n  notes: 'Filter is named `numberformat`, and uses `java.text.DecimalFormat` formatting instead of PHP-style formatting.'\n- name: 'raw'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/raw.html'\n  support: 'full'\n  page: 'raw'\n- name: 'replace'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/replace.html'\n  support: 'full'\n  page: 'replace'\n- name: 'reverse'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/reverse.html'\n  support: 'full'\n  page: 'reverse'\n- name: 'round'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/round.html'\n  support: 'none'\n- name: 'slice'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/slice.html'\n  support: 'full'\n  page: 'slice'\n- name: 'sort'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/sort.html'\n  support: 'full'\n  page: 'sort'\n- name: 'split'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/split.html'\n  support: 'full'\n  page: 'split'\n- name: 'striptags'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/striptags.html'\n  support: 'none'\n- name: 'title'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/title.html'\n  support: 'full'\n  page: 'title'\n- name: 'trim'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/trim.html'\n  support: 'full'\n  page: 'trim'\n- name: 'upper'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/upper.html'\n  support: 'full'\n  page: 'upper'\n- name: 'url_encode'\n  twigLink: 'https://twig.symfony.com/doc/2.x/filters/url_encode.html'\n  support: 'partial'\n  page: 'urlencode'\n  notes: 'Filter is named `urlencode`.'"
  },
  {
    "path": "docs/src/orchid/resources/data/twig-compatibility/functions.yml",
    "content": "- name: 'attribute'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/attribute.html'\n  support: 'none'\n- name: 'block'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/block.html'\n  support: 'full'\n  page: 'block'\n- name: 'constant'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/constant.html'\n  support: 'none'\n- name: 'cycle'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/cycle.html'\n  support: 'none'\n- name: 'date'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/date.html'\n  support: 'none'\n- name: 'dump'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/dump.html'\n  support: 'none'\n- name: 'include'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/include.html'\n  support: 'none'\n- name: 'max'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/max.html'\n  support: 'full'\n  page: 'max'\n- name: 'min'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/min.html'\n  support: 'full'\n  page: 'min'\n- name: 'parent'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/parent.html'\n  support: 'full'\n  page: 'parent'\n- name: 'random'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/random.html'\n  support: 'none'\n- name: 'range'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/range.html'\n  support: 'full'\n  page: 'range'\n- name: 'source'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/source.html'\n  support: 'none'\n- name: 'template_from_string'\n  twigLink: 'https://twig.symfony.com/doc/2.x/functions/template_from_string.html'\n  support: 'none'\n"
  },
  {
    "path": "docs/src/orchid/resources/data/twig-compatibility/operators.yml",
    "content": "- name: 'in'\n  twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#containment-operator'\n  support: 'none'\n  page: 'contains'\n  notes: '`contains` can be used instead'\n- name: 'is'\n  twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#test-operator'\n  support: 'full'\n  page: 'is'\n- name: 'Math (+, -, /, %, //, *, **)'\n  twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#math'\n  support: 'partial'\n  page: 'math'\n  notes: '`//` and `**` not supported'\n- name: 'Logic (and, or, not, (), b-and, b-xor, b-or)'\n  twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#logic'\n  support: 'partial'\n  page: 'logic'\n  notes: 'Bitwise logical operators are not supported'\n- name: 'Comparisons (==, !=, <, >, >=, <=, ===, starts with, ends with, matches)'\n  twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#comparisons'\n  support: 'partial'\n  page: 'comparisons'\n  notes: '`===`, `starts with`, `ends with`, and `matches` not supported'\n- name: 'Others (.., |, ~, ., [], ?:)'\n  twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#other-operators'\n  support: 'full'\n  page: 'others'\n  notes: 'subscript operator `[]` index starts at 0, not 1'"
  },
  {
    "path": "docs/src/orchid/resources/data/twig-compatibility/tags.yml",
    "content": "- name: 'autoescape'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/autoescape.html'\n  support: 'full'\n  page: 'autoescape'\n- name: 'block'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/block.html'\n  support: 'full'\n  page: 'block'\n- name: 'do'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/do.html'\n  support: 'none'\n- name: 'embed'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/embed.html'\n  support: 'partial'\n  page: 'embed'\n  notes: '`only` clause and `ignore missing` clause not supported'\n- name: 'extends'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/extends.html'\n  support: 'full'\n  page: 'extends'\n- name: 'filter'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/filter.html'\n  support: 'full'\n  page: 'filter'\n- name: 'flush'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/flush.html'\n  support: 'full'\n  page: 'flush'\n- name: 'for'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/for.html'\n  support: 'full'\n  page: 'for'\n- name: 'from'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/from.html'\n  support: 'full'\n  page: 'from'\n- name: 'if'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/if.html'\n  support: 'full'\n  page: 'if'\n- name: 'import'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/import.html'\n  support: 'full'\n  page: 'import'\n- name: 'include'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/include.html'\n  support: 'partial'\n  page: 'include'\n  notes: '`only` clause and `ignore missing` clause not supported'\n- name: 'macro'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/macro.html'\n  support: 'full'\n  page: 'macro'\n- name: 'sandbox'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/sandbox.html'\n  support: 'none'\n- name: 'set'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/set.html'\n  support: 'full'\n  page: 'set'\n- name: 'spaceless'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/spaceless.html'\n  support: 'none'\n- name: 'use'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/use.html'\n  support: 'none'\n- name: 'verbatim'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/verbatim.html'\n  support: 'full'\n  page: 'verbatim'\n- name: 'with'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tags/with.html'\n  support: 'none'\n\n"
  },
  {
    "path": "docs/src/orchid/resources/data/twig-compatibility/tests.yml",
    "content": "- name: 'constant'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/constant.html'\n  support: 'none'\n- name: 'defined'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/defined.html'\n  support: 'none'\n- name: 'divisible by'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/divisibleby.html'\n  support: 'none'\n- name: 'empty'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/empty.html'\n  support: 'full'\n  page: 'empty'\n- name: 'even'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/even.html'\n  support: 'full'\n  page: 'even'\n- name: 'iterable'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/iterable.html'\n  support: 'full'\n  page: 'iterable'\n- name: 'null'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/null.html'\n  support: 'full'\n  page: 'null'\n- name: 'odd'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/odd.html'\n  support: 'full'\n  page: 'odd'\n- name: 'same as'\n  twigLink: 'https://twig.symfony.com/doc/2.x/tests/sameas.html'\n  support: 'none'"
  },
  {
    "path": "docs/src/orchid/resources/homepage.md",
    "content": "---\n---\n\nPebble is a Java templating engine inspired by <a href=\"https://twig.symfony.com/\">Twig</a> and similar to the Python [Jinja Template Engine](https://palletsprojects.com/p/jinja/) syntax. It features templates inheritance and easy-to-read syntax, ships with built-in autoescaping for security, and includes integrated support for internationalization.\n\n## Features\n\n* **Rich set of built-in tags and filters**\n* **Template inheritance**: extract common areas of your content in a single ‘layout’ and make\nyour templates inherit this layout.\n* **Extensible language**: new tags, filters and functions can be added to Pebble very easily.\n\nIf you already know Twig, you can compare both engines in {{ anchor('the compatibility matrix', 'Twig Compatibility') }}.\n\n## Basic Usage\nFirst, add the following dependency to your pom.xml:\n```xml\n<dependency>\n\t<groupId>io.pebbletemplates</groupId>\n\t<artifactId>pebble</artifactId>\n\t<version>{{ site.version }}</version>\n</dependency>\n```\n\nThen create a template in your WEB-INF folder. Let's start with a base template that all\nother templates will inherit from, name it \"base.html\":\n```twig\n{% verbatim %}\n<html>\n<head>\n\t<title>{% block title %}My Website{% endblock %}</title>\n</head>\n<body>\n\t<div id=\"content\">\n\t\t{% block content %}{% endblock %}\n\t</div>\n\t<div id=\"footer\">\n\t\t{% block footer %}\n\t\t\tCopyright 2018\n\t\t{% endblock %}\n\t</div>\n</body>\n</html>\n{% endverbatim %}\n```\nThen create a template that extends base.html, call it \"home.html\":\n```twig\n{% verbatim %}\n{% extends \"base.html\" %}\n\n{% block title %} Home {% endblock %}\n\n{% block content %}\n\t<h1> Home </h1>\n\t<p> Welcome to my home page. My name is {{ name }}.</p>\n{% endblock %}\n{% endverbatim %}\n```\nNow we want to compile the template, and render it:\n```java\nPebbleEngine engine = new PebbleEngine.Builder().build();\nPebbleTemplate compiledTemplate = engine.getTemplate(\"home.html\");\n\nMap<String, Object> context = new HashMap<>();\ncontext.put(\"name\", \"Mitchell\");\n\nWriter writer = new StringWriter();\ncompiledTemplate.evaluate(writer, context);\n\nString output = writer.toString();\n```\nThe output should result in the following:\n```twig\n<html>\n<head>\n\t<title> Home </title>\n</head>\n<body>\n\t<div id=\"content\">\n\t\t<h1> Home </h1>\n\t    <p> Welcome to my home page. My name is Mitchell.</p>\n\t</div>\n\t<div id=\"footer\">\n\t\tCopyright 2018\n\t</div>\n</body>\n</html>\n```\n\nFor more information on installation and configuration, see {{ anchor('the installation guide', 'Installation and Configuration') }}.<br/>\nFor more information on basic usage, see {{ anchor('the basic usage guide', 'Basic Usage') }}.<br/>\nFor Spring Boot integration, see {{ anchor('the Spring Boot integration guide', 'Spring Boot Integration') }}."
  },
  {
    "path": "docs/src/orchid/resources/pages/CNAME",
    "content": "---\nrenderMode: raw\nusePrettyUrl: false\n---\npebbletemplates.io"
  },
  {
    "path": "docs/src/orchid/resources/pages/changelog.md",
    "content": "---\ncomponents:\n  - type: changelog\n---\n"
  },
  {
    "path": "docs/src/orchid/resources/pages/contributing.md",
    "content": "---\n---\n\n# Contributing\n\n## Help Wanted\nContributors of all types are welcome but most importantly I am looking for help with the following:\n\n- IDE Integration\n- API Feedback\n- Testing and Bug Reports\n- Performance Optimizations\n- Improving Thread Safety\nGeneral improvements are welcome, otherwise you can help tackle some of the [known issues](https://github.com/PebbleTemplates/pebble/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).\n\n## Getting Started\n- Fork the [repository](https://github.com/PebbleTemplates/pebble)\n- Make the fix.\n- Use maven to build and test:\n    - `mvn clean install` from root to build Pebble\n    - `mvn orchid:serve -P doc` from `docs/` to build and serve documentation on http://localhost:8080\n- Submit a pull request\n\n## Understanding the Code\nThere are a few major components that play a vital role in the compilation process. The main\n`PebbleEngine`, the `LexerImpl`, and the `ParserImpl`.\n\nThe `PebbleEngine` is responsible for beginning the template compilation process. It begins by passing\nthe template source to the `Lexer`.\n\nThe `Lexer` is responsible for converting the template into a bunch of `Token` objects. A token is the smallest\ndistinguishable unit in a template, i.e. it can not be broken down into more specific objects. A token can\nrepresent a delimiter (eg. `{{ '{{' }}` ), or a function name, a string, plain text, or many other things. Once the\nlexer has established the entire stream of tokens which collectively make up the entire template, it returns\nthis `TokenStream` back to the main `PebbleEngine`.\n\nThe `PebbleEngine` now sends the `TokenStream` to the `Parser` which is responsible for turning those token objects\ninto `Node` objects. The nodes are built in a hierarchical fashion and together they make up the abstract\nsyntax tree. The parser uses the help of the main `ExpressionParser` as well as the `TokenParser` objects which\nare provided by the\textensions. Each node is responsible for rendering itself during the template\nevaluation phase.\n\nThe `PebbleEngine` now has a tree of node objects, beginning with the `NodeRoot`. It will then create a\n`PebbleTemplateImpl` object with this root node as an argument. The last step before returning this template\nto the user is to invoke both the internal and the user-provided `NodeVisitor` objects on the node tree.\nNode visitors are used to process the node tree, perhaps to add extra functionality (ex. the built-in\nautoescaper uses a node visitor to wrap the expressions of nodes with an escape function). Once this\nprocessing is completed, the template is placed into the cache and finally returned to the user.\n\nThe user will then call `evaluate()` on this template which begins the evaluation process. The template\nwill call `render()` on it's root node which in turn will call `render()` on each of it's children nodes.\nThis process recurses throughout the whole node tree until all nodes have rendered themselves to the\nprovided `Writer` object.\n\n## Documentation\n\nThe documentation website is generated using [Orchid](https://orchid.netlify.com/). \n\nThe documentation files are under the `src/orchid/resources` directory.\n\n## Contributing Code\nCurrently there aren't any formal guidelines. Just ensure that your changes include any\nnecessary unit tests and send me a pull request on [github](https://github.com/PebbleTemplates/pebble)!\n\n## Standards and Guidelines\n\n### Code Formatting Standards\n\n1. Use [Google Style Guide](https://google.github.io/styleguide/javaguide.html) formatting conventions.\n   1. Configuring IntelliJ to use the Google Style Guide Formatter.\n      1. Clone the [Google Style Git Repo](https://github.com/google/styleguide).\n      2. Open **Preferences -> Editor -> Code Style -> Java**. \n      4. Click on the gear and select **Import Scheme -> IntelliJ IDEA code style XML** and import the file **intellij-java-google-style.xml** from the [Google Style Git Repo](https://github.com/google/styleguide).\n   2. Configuring Eclipse to use the Google Style Guide Formatter.\n      1. Clone the [Google Style Git Repo](https://github.com/google/styleguide).\n      2. Select **Preference -> Java -> Code Style -> Formatter**.\n      3. Select **Import** and specify the file **eclipse-java-google-style.xml** from the [Google Style Git Repo](https://github.com/google/styleguide).\n\n### Unit Test Guidelines\n\n1. Use [AssertJ]() for all Unit and Integration tests. \n2. A javadoc description should be included for each test method.\n3. The purpose of each test should be clear from the test method name.\n\n## Acknowledgements\nThanks to all the following contributors who are helping to make Pebble the best template engine available for Java. \n\n{%- for contributor in data.contributors %}\n* [{{ contributor.name }}]({{ contributor.link }})\n{%- endfor %}\n"
  },
  {
    "path": "docs/src/orchid/resources/pages/twig-compatibility.peb",
    "content": "---\ntitle: Twig Compatibility\n---\n\nWhile Pebble was inspired by and generally has high compatibility with Twig, it is neither a complete nor an exact Twig\nimplementation. There are features of Twig not supported by Pebble, there are features implemented in Pebble that are\nnot supported by Twig, and some features might behave differently or use a different name or syntax. This page outlines\nthe similarities and differences between Pebble and Twig.\n\n{{ compat('Tags', data['twig-compatibility'].tags ) }}\n{{ compat('Filters', data['twig-compatibility'].filters ) }}\n{{ compat('Functions', data['twig-compatibility'].functions ) }}\n{{ compat('Tests', data['twig-compatibility'].tests ) }}\n{{ compat('Operators', data['twig-compatibility'].operators ) }}\n\n{% macro compat(type, items) %}\n<br>\n<br>\n<h2>{{ type }}</h2>\n<table>\n    <thead>\n    <tr>\n        <td>Name</td>\n        <td>Pebble Support</td>\n        <td>Notes</td>\n    </tr>\n    </thead>\n    <tbody>\n    {% for item in items %}\n    <tr>\n        <td><a href=\"{{ item.twigLink }}\">{{ item.name }}</a></td>\n        <td>\n            {{ item.support }}\n            {% if item.page is not empty %}\n                {{ anchor('(docs)', item.page) }}\n            {% endif %}\n        </td>\n        <td>{{ item.notes | compileAs('md') }}</td>\n    </tr>\n    {% endfor %}\n    </tbody>\n</table>\n{% endmacro %}\n"
  },
  {
    "path": "docs/src/orchid/resources/templates/includes/sidebar.peb",
    "content": "<div id=\"sidebar\">\n    <div class=\"inner\">\n\n        <!-- Search -->\n        <section id=\"search\" class=\"alt\">\n            <form data-orchid-search=\"{{ (searchIndices is not empty) ? searchIndices|join(',') : '' }}\">\n                <input type=\"text\" name=\"query\" id=\"query\" placeholder=\"Search\"/>\n            </form>\n            <div class=\"box\" id=\"search-results\" data-orchid-search-results style=\"display: none;\">\n                <b>Search Results</b>\n                <ul></ul>\n            </div>\n            <div class=\"box\" id=\"search-progress\" data-orchid-search-progress style=\"display: none;\">\n                <div class=\"loader\">Loading...</div>\n            </div>\n        </section>\n\n        <!-- Menu -->\n        <nav id=\"siteNav\" class=\"menu\">\n            <header class=\"major\">\n                <h2>{{ site.about.siteName }}</h2>\n            </header>\n            <ul class=\"top-level\">\n                {% for menuItem in theme.menu.getMenuItems(page) %}\n                    {% include 'includes/menuItem' with {\"menuItem\": menuItem} %}\n                {% endfor %}\n            </ul>\n        </nav>\n        {% if not page.menu.isEmpty() %}\n        <nav class=\"menu\">\n            <header class=\"major\">\n                <h2>{{ page.title }}</h2>\n            </header>\n            <ul class=\"top-level\">\n                {% for menuItem in page.menu.getMenuItems(page) %}\n                    {% include 'includes/menuItem' with {\"menuItem\": menuItem} %}\n                {% endfor %}\n            </ul>\n        </nav>\n        {% endif %}\n\n        <!-- Section -->\n        <section>\n            <p class=\"text-center\">\n                Currently {{ site.version }}.\n            </p>\n        </section>\n\n        <!-- Footer -->\n        <footer id=\"footer\">\n            <p class=\"copyright text-center\">\n                &copy; PebbleTemplates. All rights reserved.\n                <br>\n                Site created with <a href=\"https://orchid.netlify.com\">Orchid </a>,\n                design by <a href=\"https://html5up.net\">HTML5 UP</a>.\n            </p>\n        </footer>\n\n    </div>\n</div>"
  },
  {
    "path": "docs/src/orchid/resources/templates/layouts/index.peb",
    "content": "<!DOCTYPE HTML>\n<!--\n    Editorial by HTML5 UP\n    html5up.net | @ajlkn\n    Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)\n-->\n<html>\n<head>\n{% include '?trackingHeadStart' %}\n{% head %}\n<link rel=\"icon\" type=\"image/png\" href=\"favicon-32x32.png\" sizes=\"32x32\" />\n<link rel=\"icon\" type=\"image/png\" href=\"favicon-16x16.png\" sizes=\"16x16\" />\n{% styles %}\n{% include '?trackingHeadEnd' %}\n</head>\n<body class=\"is-loading\">\n{% include '?trackingBodyStart' %}\n\n<!-- Wrapper -->\n<div id=\"wrapper\">\n\n    <!-- Main -->\n    <div id=\"main\">\n        <a class=\"github-fork-ribbon\" href=\"https://github.com/PebbleTemplates\" data-ribbon=\"Fork me on GitHub\" title=\"Fork me on GitHub\">Fork me on GitHub</a>\n        <div class=\"inner\">\n\n            <!-- Header -->\n            {% block innerHeader %}\n                {% include 'includes/header' %}\n            {% endblock %}\n            {% block banner %}\n            {% endblock %}\n            {% block pageContent %}\n                <section id=\"pageContent\">\n                    <header class=\"major\"><h2>{{ page.title }}</h2></header>\n                    <article>\n                        {% page %}\n                    </article>\n                </section>\n            {% endblock %}\n        </div>\n        <hr>\n\n        <!-- Page Footer -->\n        {% include 'includes/footer.peb' %}\n    </div>\n\n    <!-- Sidebar -->\n    {% include 'includes/sidebar.peb' %}\n</div>\n\n{% scripts %}\n<script>\n(function ($) {\n\t$(function () {\n        const href = window.location.href.replace(/\\/+$/, '');\n\n        if (!href.includes('/wiki/')) {\n            return;\n        }\n\n        const a = document.querySelector('#siteNav li li a[href=\"' + href + '\"]');\n        const ul = a.closest('ul');\n        const li = ul.closest('li');\n        const span = li.querySelector(':scope > span');\n\n        a.classList.add('active');\n        a.style.color = '#72A6D0';\n        span.style.color = '#72A6D0';\n\n        setTimeout(function () {\n            span.click();\n        }, 0);\n    });\n}(jQuery));\n</script>\n{% include '?trackingBodyEnd' %}\n</body>\n</html>"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/abbreviate.md",
    "content": "# `abbreviate`\nThe `abbreviate` filter will abbreviate a string using an ellipsis. It takes one argument which is the max\nwidth of the desired output including the length of the ellipsis.\n```twig\n{{ \"this is a long sentence.\" | abbreviate(7) }}\n```\nThe above example will output the following:\n```twig\nthis...\n```\n\n## Arguments\n- length"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/abs.md",
    "content": "# `abs`\nThe `abs` filter is used to obtain the absolute value.\n\n```twig\n{{ -7 | abs }}\n\n{# output: 7 #}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/base64decode.md",
    "content": "---\n---\n\n# `base64decode`\nThe `base64decode` filter takes the given input, Base64-decodes it, if possible, and returns the byte array converted to UTF-8 String.\nApplying the filter on an incorrect base64-encoded string will throw an exception. \n\n```twig\n{% verbatim %}{{ \"dGVzdA==\" | base64decode }}{% endverbatim %}\n```\nThe above example will output the following:\n```\ntest\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/base64encode.md",
    "content": "---\n---\n\n# `base64encode`\nThe `base64encode` filter takes the given input, converts it to an UTF-8 String (`.toString()`) and Base64-encodes it.\n\n```twig\n{% verbatim %}{{ \"test\" | base64encode }}{% endverbatim %}\n```\nThe above example will output the following:\n```\ndGVzdA==\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/capitalize.md",
    "content": "---\n---\n\n# `capitalize`\nThe `capitalize` filter will capitalize the first letter of the string.\n```twig\n{% verbatim %}\n{{ \"article title\" | capitalize }}\n{% endverbatim %}\n```\nThe above example will output the following:\n```twig\nArticle title\n```\nSee also: {{ anchor('title') }}"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/date.md",
    "content": "# `date`\nThe `date` filter formats a date in a variety of formats. It can handle old-school `java.util.Date`,\nJava 8 `java.time` constructs like `OffsetDateTime` and timestamps in milliseconds from the epoch.\nThe filter will construct a `java.text.SimpleDateFormat` or `java.time.format.DateTimeFormatter` using the provided\npattern and then use this newly created format to format the provided date object. If you don't provide a pattern,\neither `DateTimeFormatter.ISO_DATE_TIME` or `yyyy-MM-dd'T'HH:mm:ssZ` will be used.\n\n```twig\n{{ user.birthday | date(\"yyyy-MM-dd\") }}\n```\n\nAn alternative way to use this filter is to use it on a string but then provide two arguments:\nthe first is the desired pattern for the output, and the second is the existing format used to parse the\ninput string into a `java.util.Date` object.\n```twig\n{{ \"July 24, 2001\" | date(\"yyyy-MM-dd\", existingFormat=\"MMMM dd, yyyy\") }}\n```\nThe above example will output the following:\n```twig\n2001-07-24\n```\n\n## Time zones\n\nIf the provided date has time zone info (e.g. `OffsetDateTime`) then it will be used. If the provided date has no\ntime zone info, by default the system time zone will be used. If you need to use a specific\ntime zone then you can pass in a `timeZone` parameter any string that's understood by `ZoneId` / `ZoneInfo`:\n```twig\n{# the timeZone parameter will be ignored #}\n{{ someOffsetDateTime | date(\"yyyy-MM-dd'T'HH:mm:ssX\", timeZone=\"UTC\") }}\n{# the provided time zone will override the system default #}\n{{ someInstant | date(\"yyyy-MM-dd'T'HH:mm:ssX\", timeZone=\"Pacific/Funafuti\") }}\n```\n\n## Arguments\n- format\n- existingFormat\n- timeZone\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/default.md",
    "content": "## `default`\nThe `default` filter will render a default value if and only if the object being filtered is empty.\nA variable is empty if it is null, an empty string, an empty collection, or an empty map.\n```twig\n{{ user.phoneNumber | default(\"No phone number\") }}\n```\nIn the following example, if `foo`, `bar`, or `baz` are null the output will become an empty string which is a perfect use case for the default filter:\n```twig\n{{ foo.bar.baz | default(\"No baz\") }}\n```\nNote that the default filter will suppress any `AttributeNotFoundException` exceptions that will usually be thrown when `strictVariables` is set to `true`.\n\n## Arguments\n- default\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/escape.md",
    "content": "---\n---\n\n# `escape`\nThe `escape` filter will turn special characters into safe character references in order to avoid XSS\nvulnerabilities. This filter will typically only need to be used if you've turned off autoescaping.\n```twig\n{% verbatim %}\n{{ \"<div>\" | escape }}\n{# output: &lt;div&gt; #}\n{% endverbatim %}\n```\nPlease read the {{ anchor('escaping guide', 'Escaping') }} for more information about escaping.\n\n## Arguments\n- strategy"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/first.md",
    "content": "# `first`\nThe `first` filter will return the first item of a collection, or the first letter of a string.\n```twig\n{{ users | first }}\n{# will output the first item in the collection named 'users' #}\n\n{{ 'Mitch' | first }}\n{# will output 'M' #}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/format.md",
    "content": "# `format`\nThe `format` filter formats a string by replacing placeholders with the provided arguments (placeholders follows the `String.format` notation).\n```twig\n{{ 'Hello %s!' | format('World') }}\n{# Hello World! #}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/join.md",
    "content": "# `join`\nThe `join` filter will concatenate all items of a collection into a string. An optional argument can be given\nto be used as the separator between items.\n```twig\n{#\n    List<String> names = new ArrayList<>();\n    names.add(\"Alex\");\n    names.add(\"Joe\");\n    names.add(\"Bob\");\n#}\n{{ names | join(',') }}\n{# will output: Alex,Joe,Bob #}\n```\n\n## Arguments\n- separator"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/last.md",
    "content": "# `last`\nThe `last` filter will return the last item of a collection, or the last letter of a string.\n```twig\n{{ users | last }}\n{# will output the last item in the collection named 'users' #}\n\n{{ 'Mitch' | last }}\n{# will output 'h' #}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/length.md",
    "content": "# `length`\nThe `length` filter returns the number of items of collection, map or the length of a string:\n\n```twig\n{% if users|length > 10 %}\n    ...\n{% endif %}\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/lower.md",
    "content": "# `lower`\nThe `lower` filter makes an entire string lower case.\n```twig\n{{ \"THIS IS A LOUD SENTENCE\" | lower }}\n```\nThe above example will output the following:\n```twig\nthis is a loud sentence\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/nl2br.md",
    "content": "# `nl2br`\n\nThe `nl2br` filter converts newline characters (`\\r`, `\\n`, `\\r\\n`) in a string to HTML line break tags (`<br />`). This\nis useful when you want to preserve line breaks in text when displaying it in a web page.\n\n```pebble\n{{ \"I like Pebble.\\nYou will like it too.\"|nl2br }}\n{# outputs\n    I like Pebble.<br />You will like it too.\n#}\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/numberformat.md",
    "content": "# `numberformat`\nThe `numberformat` filter is used to format a decimal number. Behind the scenes it uses `java.text.DecimalFormat`.\n```twig\n{{ 3.141592653 | numberformat(\"#.##\") }}\n```\nThe above example will output the following:\n```twig\n3.14\n```\n\n## Arguments\n- format"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/raw.md",
    "content": "---\n---\n\n# `raw`\nThe `raw` filter prevents the output of an expression from being escaped by the autoescaper.\nThe `raw` filter must be the very last operation performed within the expression otherwise the\nautoescaper will deem the expression as potentially unsafe and escape it regardless.\n\n```twig\n{% verbatim %}\n{% set danger = \"<div>\" %}\n{{ danger | upper | raw }}\n{# ouptut: <DIV> #}\n{% endverbatim %}\n```\n\nIf the `raw` filter is not the last operation performed then the expression will be escaped:\n```twig\n{% verbatim %}\n{% set danger = \"<div>\" %}\n{{ danger | raw | upper }}\n{# output: &lt;DIV&gt; #}\n{% endverbatim %}\n```\nPlease read the {{ anchor('escaping guide', 'Escaping') }} for more information about escaping."
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/replace.md",
    "content": "# `replace`\nThe 'replace' filter formats a given string by replacing the placeholders (placeholders are free-form):\n```twig\n{{ \"I like %this% and %that%.\" | replace({'%this%': foo, '%that%': \"bar\"}) }}\n```\n\n## Arguments\n- placeholders to replace"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/reverse.md",
    "content": "# `reverse`\nThe 'reverse' filter reverses a List:\n```twig\n{% for user in users | reverse %} {{ user }} {% endfor %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/rsort.md",
    "content": "# `rsort`\nThe `rsort` filter will sort a list in reversed order. The items of the list must implement `Comparable`.\n```twig\n{% for user in users | rsort %}\n\t{{ user.name }}\n{% endfor %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/sha256.md",
    "content": "---\n---\n\n# `sha256`\nThe `sha256` filter returns the SHA-256 hash of the given UTF-8 String.\n\n```twig\n{% verbatim %}{{ \"test\" | sha256 }}{% endverbatim %}\n```\nThe above example will output the following:\n```\n9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/slice.md",
    "content": "# `slice`\nThe `slice` filter returns a portion of a list, array, or string.\n```twig\n{{ ['apple', 'peach', 'pear', 'banana'] | slice(1,3) }}\n{# results in: [peach, pear] #}\n\n\n{{ 'Mitchell' | slice(1,3) }}\n{# results in: 'it' #}\n```\n\n## Arguments\n- `fromIndex`: 0-based and inclusive\n- `toIndex`: 0-based and exclusive\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/sort.md",
    "content": "# `sort`\nThe `sort` filter will sort a list. The items of the list must implement `Comparable`.\n```twig\n{% for user in users | sort %}\n\t{{ user.name }}\n{% endfor %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/split.md",
    "content": "# `split`\nThe `split` filter splits a string by the given delimiter and returns a list of strings.\n```twig\n{% set foo = \"one,two,three\" | split(',') %}\n{# foo contains ['one', 'two', 'three'] #}\n```\n\nYou can also pass a limit argument:\n- If `limit` is positive, then the pattern will be applied at most n - 1 times, the array's length will be no greater than n, and the array's last entry will contain all input beyond the last matched delimiter;\n- If `limit` is negative, then the pattern will be applied as many times as possible and the array can have any length;\n- If `limit` is zero, then the pattern will be applied as many times as possible, the array can have any length, and trailing empty strings will be discarded;\n\n```twig\n{% set foo = \"one,two,three,four,five\" | split(',', 3) %}\n{# foo contains ['one', 'two', 'three,four,five'] #}\n```\n\n## Arguments\n- delimiter: The delimiter\n- limit: The limit argument"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/title.md",
    "content": "---\n---\n\n# `title`\nThe `title` filter will capitalize the first letter of each word.\n```twig\n{% verbatim %}\n{{ \"article title\" | title }}\n{% endverbatim %}\n```\nThe above example will output the following:\n```twig\nArticle Title\n```\n\nSee also: {{ anchor('capitalize') }}\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/trim.md",
    "content": "# `trim`\nThe `trim` filter is used to trim whitespace off the beginning and end of a string.\n```twig\n{{ \"    This text has too much whitespace.    \" | trim }}\n```\nThe above example will output the following:\n```twig\nThis text has too much whitespace.\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/upper.md",
    "content": "# `upper`\nThe `upper` filter makes an entire string upper case.\n\n```twig\n{{ \"this is a quiet sentence.\" | upper }}\n```\nThe above example will output the following:\n```twig\nTHIS IS A QUIET SENTENCE.\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/filter/urlencode.md",
    "content": "# `urlencode`\n\nThe `urlencod` translates a string into `application/x-www-form-urlencoded` format using the \"UTF-8\" encoding scheme.\n\n```twig\n{{ \"The string ü@foo-bar\" | urlencode }}\n```\n\nThe above example will output the following:\n```twig\nThe+string+%C3%BC%40foo-bar\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/function/blockFunction.md",
    "content": "---\n---\n\n# `block`\nThe `block` function is used to render the contents of a block more than once. It is not to be confused\nwith the block *tag* which is used to declare blocks.\n\nThe following example will render the contents of the \"post\" block twice; once where it was declared\nand again using the `block` function:\n```twig\n{% verbatim %}\n{% block \"post\" %} content {% endblock %}\n\n{{ block(\"post\") }}\n{% endverbatim %}\n```\nThe above example will output the following:\n```twig\ncontent\n\ncontent\n```\n\n## Performance Warning\nThe `block` function will impair the use of the {{ anchor('flush') }} tag used within the block being rendered.\nIt is typically okay for a block to use the `flush` tag which will flush the already-rendered\ncontent to the user-provided `Writer` but the block function will internally use it's own `StringWriter` and\ntherefore flushing inside the block will no longer do any good (nor will it do harm)."
  },
  {
    "path": "docs/src/orchid/resources/wiki/function/i18n.md",
    "content": "# `i18n`\nThe `i18n` function is used to retrieve messages from a locale-specific `ResourceBundle`.\nEvery `PebbleTemplate` is assigned a default locale from the `PebbleEngine`. At the point of evaluation, this\nlocale can be changed with an argument to the `evaluate(...)` method of the individual template.\n\nThe `i18n` function wraps around `ResourceBundle.getBundle(name, locale).getObject(key)`.\nThe first argument to the `i18n` function is the name of the bundle and the second argument is the key\nwithin the bundle.\n\n```twig\n{{ i18n(\"messages\",\"greeting\") }}\n```\n\nThe above example assumes you have `messages.properties` on your classpath and that that file contains\na key by the name of `greeting`. If the locale of that template was `es_US` for example, it would look for a\n`message_es_US.properties` file instead.\n\nGoing a little further, you can use variables within your message and pass a list of params to this\nfunction which will replace your variables using `MessageFormat`:\n```twig\n{# greeting.someone=Hello, {0} #}\n{{ i18n(\"messages\",\"greeting\", \"Jacob\") }}\n\n{# output: Hello, Jacob #}\n```\n\n## Arguments\n- bundle\n- key\n- params"
  },
  {
    "path": "docs/src/orchid/resources/wiki/function/max.md",
    "content": "# `max`\nThe `max` function will return the largest of it's numerical arguments.\n```twig\n{{ max(user.age, 80) }}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/function/min.md",
    "content": "# `min`\nThe `min` function will return the smallest of it's numerical arguments.\n```twig\n{{ min(user.age, 80) }}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/function/parent.md",
    "content": "# `parent`\n\nThe `parent` function is used inside of a block to render the content that the parent template would\nhave rendered inside of the block had the current template not overriden it. It is similar to Java's `super` keyword.\n\nLet's assume you have a template, \"parent.peb\" that looks something like this:\n```twig\n{% block \"content\" %}\n\tparent contents\n{% endblock %}\n```\nAnd then you have another template, \"child.peb\" that extends \"parent.peb\":\n```twig\n{% extends \"parent.peb\" %}\n\n{% block \"content\" %}\n\tchild contents\n\t{{ parent() }}\n{% endblock %}\n```\nThe output will look something like the following:\n```twig\nparent contents\nchild contents\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/function/range.md",
    "content": "# `range`\nThe `range` function will return a list containing an arithmetic progression of numbers:\n```twig\n{% for i in range(0, 3) %}\n    {{ i }},\n{% endfor %}\n\n{# outputs 0, 1, 2, 3, #}\n```\n\nWhen step is given (as the third parameter), it specifies the increment (or decrement):\n```twig\n{% for i in range(0, 6, 2) %}\n    {{ i }},\n{% endfor %}\n\n{# outputs 0, 2, 4, 6, #}\n```\n\nPebble built-in .. operator is just a shortcut for the range function with a step of 1+\n```twig\n{% for i in 0..3 %}\n    {{ i }},\n{% endfor %}\n\n{# outputs 0, 1, 2, 3, #}\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/guide/basic-usage.md",
    "content": "---\n---\n\n# Basic Usage\n\n## Introduction\nPebble templates can be used to generate any sort of textual output. It is typically used to generate HTML but it can\nalso be used to create CSS, XML, JS, etc. A template itself will contain whatever language you are attempting to output\nalongside Pebble-specific features and syntax. Here is a simple example that will generate a trivial HTML page:\n\n```twig\n{% verbatim %}\n<html>\n\t<head>\n\t\t<title>{{ websiteTitle }}</title>\n\t</head>\n\t<body>\n\t\t{{ content }}\n\t</body>\n</html>\n{%- endverbatim %}\n```\nWhen you evaluate the template you will provide it with a \"context\" which is just a map of variables.\nThis context should include the two variables above, `websiteTitle` and `content`.\n\n## Set Up\nYou will want to begin by creating a PebbleEngine object which is responsible for compiling your templates:\n```java\nPebbleEngine engine = new PebbleEngine.Builder().build();\n```\nAnd now, with your new PebbleEngine instance you can start compiling templates:\n```java\nPebbleTemplate compiledTemplate = engine.getTemplate(\"templates/home.html\");\n```\nFinally, simply provide your compiled template with a java.io.Writer object and a Map of variables (the context)\nto get your output!\n```java\nWriter writer = new StringWriter();\n\nMap<String, Object> context = new HashMap<>();\ncontext.put(\"websiteTitle\", \"My First Website\");\ncontext.put(\"content\", \"My Interesting Content\");\n\ncompiledTemplate.evaluate(writer, context);\n\nString output = writer.toString();\n```\n\n## Syntax Reference\nThere are two primary delimiters used within a Pebble template: `{% verbatim %}{{ ... }}{% endverbatim %}` and `{% verbatim %}{% ... %}{% endverbatim %}`. The first set of delimiters\nwill output the result of an expression. Expressions can be very simple (ex. a variable name) or much more complex.\nThe second set of delimiters is used to change the control flow of the template; it can contain an if-statement,\ndefine a parent template, define a new block, etc.\n\n## Variables\nYou can print variables directly to the output; for example, if the context contains a variable called `foo` which is a\nString with the value \"bar\" you can do the following which will output \"bar\".\n```twig\n{% verbatim %}{{ foo }}{% endverbatim %}\n```\nYou can use the dot (.) notation to access attributes of variables. If the attribute contains any atypical\ncharacters, you can use the subscript notation ([]) instead.\n```twig\n{% verbatim %}\n{{ foo.bar }}\n{{ foo[\"bar\"] }}\n{%- endverbatim %}\n```\nBehind the scenes `foo.bar` will attempt the following techniques to to access the `bar` attribute of the `foo`\nvariable:\n- If `foo` is a Map, `foo.get(\"bar\")`\n- `foo.getBar()`\n- `foo.isBar()`\n- `foo.hasBar()`\n- `foo.bar()`\n- `foo.bar`\n\nAdditionally, if `foo` is a List, then `foo[0]` can be used instead of `foo.get(0)`.\n\nIf the value of variable (or attribute) is null it will output an empty string.\n\n## Type Safety\nPebble templates are dynamically typed and any possible type safety issues won't occur until the actual runtime\nevaluation of your templates. Pebble does however allow you to choose how to handle type safety issues with the use\nof it's `strictVariables` setting. By default, `strictVariables` is set to `false` which means that the following:\n```twig\n{% verbatim %}{{ foo.bar }}{% endverbatim %}\n```\nwill print an empty string even if the object `foo` does not actually have an attribute called `bar`.\nIf `strictVariables` is set to true, the above expression would throw an exception.\n\nWhen `strictVariables` is set to false your expressions are also null safe. The following expression will print an\nempty string even if foo and/or bar are null:\n```twig\n{% verbatim %}{{ foo.bar.baz }}{% endverbatim %}\n```\nThe {{ anchor('default') }} filter might come in handy for the above situations.\n\n## Filters\n\nOutput can be further modified with the use of filters. Filters are separated from the variable using a\npipe symbol (`|`) and may have optional arguments in parentheses. Multiple filters can be chained and the output\nof one filter is applied to the next.\n```twig\n{% verbatim %}{{ \"If life gives you lemons, eat lemons.\" | upper | abbreviate(13) }}{% endverbatim %}\n```\nThe above example will output the following:\n```twig\nIF LIFE GI...\n```\n\n## Functions\nWhereas filters are intended to modify existing content/variables, functions are intended to generate new content.\nSimilar to other programming languages, functions are invoked via their name followed by parentheses (`()`).\n```twig\n{% verbatim %}{{ max(user.score, highscore) }}{% endverbatim %}\n```\n\n## Control Structure\nPebble provides several tags to control the flow of your template, two of the main ones being the {{ anchor('for') }} loop,\nand {{ anchor('if') }} statements.\n```twig\n{% verbatim %}\n{% for article in articles %}\n    <h3>{{ article.title }}</h3>\n    <p>{{ article.content }}</p>\n{% else %}\n    <p> There are no articles. </p>\n{% endfor %}\n{%- endverbatim %}\n```\n```twig\n{% verbatim %}\n{% if category == \"news\" %}\n    {{ news }}\n{% elseif category == \"sports\" %}\n    {{ sports }}\n{% else %}\n    <p>Please select a category</p>\n{% endif %}\n{%- endverbatim %}\n```\n\n## Including other Templates\nThe {{ anchor('include') }} tag is used to include the rendered output of one template into another.\n```twig\n{% verbatim %}\n<div class=\"sidebar\">\n\t{% include \"advertisement.html\" %}\n</div>\n{%- endverbatim %}\n```\n\n## Template Inheritance\nTemplate inheritance is the most powerful feature of Pebble. It allows templates to override sections of their parent\ntemplate. In your parent template you define \"blocks\" which are the sections that are allowed to be overriden.\n\nFirst let us look at an example of a parent template:\n```twig\n{% verbatim %}\n<html>\n<head>\n\t<title>{% block title %}My Website{% endblock %}</title>\n</head>\n<body>\n\t<div id=\"content\">\n\t\t{% block content %}{% endblock %}\n\t</div>\n\t<div id=\"footer\">\n\t\t{% block footer %}\n\t\t\tCopyright 2013\n\t\t{% endblock %}\n\t</div>\n</body>\n</html>\n{% endverbatim %}\n```\nIn the above example, we have used the {{ anchor('block') }} tag to define several sections that child templates are\nallowed to override.\n\nA child template might look like this:\n\n```twig\n{% verbatim %}\n{% extends \"parent.html\" %}\n\n{% block title %} Home {% endblock %}\n\n{% block content %}\n\t<h1> Home </h1>\n\t<p> Welcome to my home page.</p>\n{% endblock %}\n{%- endverbatim %}\n```\n\nThe first line uses the {{ anchor('extends') }} tag to declare the parent template. The extends tag should be the\nfirst tag in the template and there can only be one.\n\nEvaluating the child template will produce the following output:\n\n```twig\n<html>\n<head>\n\t<title>Home</title>\n</head>\n<body>\n\t<div id=\"content\">\n\t\t<h1> Home </h1>\n\t\t<p> Welcome to my home page.</p>\n\t</div>\n\t<div id=\"footer\">\n\t\tCopyright 2013\n\t</div>\n</body>\n</html>\n```\n\nYou may have noticed that in the above example, because the child template doesn't override the `footer` block,\nthe value from the parent is used instead.\n\nDynamic inheritance is possible by using an expression with the `extends` tag:\n```twig\n{% verbatim %}{% extends ajax ? 'ajax.html' : 'base.html' %}{% endverbatim %}\n```\n\n## Macros\nMacros are lightweight and reusable template fragments. A macro is defined via the {{ anchor('macro') }} tag:\n```twig\n{% verbatim %}\n{% macro input(type, name) %}\n\t<input type=\"{{ type }}\" name=\"{{ name }}\" />\n{% endmacro %}\n{% endverbatim %}\n```\nAnd the macro will be invoked just like a function:\n```twig\n{% verbatim %}{{ input(\"text\", \"name\", \"Mitchell\") }}{% endverbatim %}\n```\nChild templates will have access to macros defined in a parent template. To use macros located in a completely\ndifferent template, you can use the {{ anchor('import') }}) tag. A macro does not have access to the main context; the\nonly variables it can access are it's local arguments.\n\n## Named Arguments\nUsing named arguments allows you to be more explicit with the values you are passing to a filter, function, test or\nmacro. They also allow you to avoid specifying arguments for which you don't want to change the default value.\n```twig\n{% verbatim %}{{ stringDate | date(existingFormat=\"yyyy-MMMM-d\", format=\"yyyy/MMMM/d\") }}{% endverbatim %}\n```\n\nPositional arguments can be used in conjunction with named arguments but all positional arguments must come before\nany named arguments:\n```twig\n{% verbatim %}{{ stringDate | date(\"yyyy/MMMM/d\", existingFormat=\"yyyy-MMMM-d\") }}{% endverbatim %}\n```\nMacros are a great use case for named arguments because they also allow you to define default values for\nunused arguments:\n```twig\n{% verbatim %}\n{% macro input(type=\"text\", name, value) %}\n\t<input type=\"{{ type }}\" name=\"{{ name }}\" value=\"{{ value }}\" />\n{% endmacro %}\n\n{{ input(name=\"country\") }}\n\n{# will output: <input type=\"text\" name=\"country\" value=\"\" /> #}\n{%- endverbatim %}\n```\n\n## Escaping\n[XSS vulnerabilites](https://en.wikipedia.org/wiki/Cross-site_scripting) are the most common types of security\nvulnerabilities in web applications and in order to avoid them you must escape potentially unsafe data before\npresenting it to the end user. Pebble provides autoescaping of all such data which is enabled by default.\nAutoescaping can be turned off, in which case Pebble provides an escape filter for more fine-grained manual escaping.\n\nThe following is an example of how autoescaping will escape your context variables:\n```twig\n{% verbatim %}\n{% set danger = \"<br>\" %}\n{{ danger }}\n\n{# will output: &lt;br&gt; #}\n{%- endverbatim %}\n```\nIf autoescaping is disabled you can still use the {{ anchor('escape') }} filter to aid with manual escaping:\n```twig\n{% verbatim %}\n{% set danger = \"<br>\" %}\n{{ danger | escape }}\n\n{# will output: &lt;br&gt; #}\n{%- endverbatim %}\n```\nBy default, the autoescaping mechanism and the escape filter assume that it is escaping within an HTML context.\nYou may want to use an alternate escaping strategy depending on the context:\n```twig\n{% verbatim %}\n{% set danger = \"alert(...)\" %}\n<script>var username=\"{{ danger | escape(strategy=\"js\") }}\"</script>\n{%- endverbatim %}\n```\nSee the {{ anchor('escaping guide', 'Escaping') }} for more information on how autoescaping works, how to disable it, and the various\nescaping strategies that are available.\n\n## Whitespace\nThe first newline after a pebble tag is automatically ignored; all other whitespace is ignored by Pebble and will be\nincluded in the rendered output.\n\nPebble provides a whitespace control modifier to trim leading or trailing whitespace adjacent to any pebble tag.\n```twig\n{% verbatim %}\n<p> \t\t{{- \"no whitespace\" -}}\t\t</p>\n{# output: \"<p>no whitespace</p>\" #}\n{%- endverbatim %}\n```\nIt is also possible to only use the modifier on one side of the tag:\n```twig\n{% verbatim %}\n<p> \t\t{{- \"no leading whitespace\" }}\t\t</p>\n{# output: \"<p>no whitespace\t\t</p>\" #}\n{%- endverbatim %}\n```\n\n## Comments\nYou can comment out any part of the template using the `{# ... #}` delimiters. These comments will not appear in\nthe rendered output.\n```twig\n{% verbatim %}\n{# THIS IS A COMMENT #}\n{% for article in articles %}\n\t<h3>{{ article.title }}</h3>\n\t<p>{{ article.content }}</p>\n{% endfor %}\n{%- endverbatim %}\n```\n\n## Expressions\nExpressions in a Pebble template are very similar to expressions found in Java.\n\n### Literals\nThe simplest form of expressions are literals. Literals are representations for Java types such as strings and numbers.\n- `\"Hello World\"`: Everything between two double or single quotes is a string. You can use a backslash to escape\nquotation marks within the string.\n- `\"Hello #{who}\"`: String interpolation is also possible using `#{}` inside quotes. In this example,\nif the value of the variable `who` is `\"world\"`, then the expression will be evaluated to `\"Hello world\"`.\n- `100 + 10l * 2.5`: Integers, longs and floating point numbers are similar to their Java counterparts.\n- `true` / `false`: Boolean values equivalent to their Java counterparts.\n- `null`: Represents no specific value, similar to it's Java counterpart. `none` is an alias for null.\n\n### Collections\nBoth lists and maps can be created directly within the template.\n- `[\"apple\", \"banana\", \"pear\"]`: A list of strings\n- `{\"apple\":\"red\", \"banana\":\"yellow\", \"pear\":\"green\"}`: A map of strings\n\nThe collections can contain expressions.\n\n### Math\nPebble allows you to calculate values using some basic mathematical operators. The following operators are supported:\n- `+`: Addition\n- `-`: Subtraction\n- `/`: Division\n- `%`: Modulus\n- `*`: Multiplication\n\n### Logic\nYou can combine multiple expressions with the following operators:\n- `and`: Returns true if both operands are true\n- `or`: Returns true if either operand is true\n- `not`: Negates an expression\n- `(...)`: Groups expressions together\n\n### Comparisons\nThe following comparison operators are supported in any expression: `==`, `!=`, `<`, `>`, `>=`, and `<=`.\n```twig\n{% verbatim %}\n{% if user.age >= 18 %}\n\t...\n{% endif %}\n{%- endverbatim %}\n```\n\n### Tests\nThe `is` operator performs tests. Tests can be used to test an expression for certain qualities.\nThe right operand is the name of the test:\n```twig\n{% verbatim %}\n{% if 3 is odd %}\n\t...\n{% endif %}\n{%- endverbatim %}\n```\nTests can be negated by using the is not operator:\n```twig\n{% verbatim %}\n{% if name is not null %}\n\t...\n{% endif %}\n{%- endverbatim %}\n```\n\n### Conditional (Ternary) Operator\nThe conditional operator is similar to its Java counterpart:\n```twig\n{% verbatim %}{{ foo ? \"yes\" : \"no\" }}{% endverbatim %}\n```\n\n### Operator Precedence\nIn order from highest to lowest precedence:\n- `.`\n- `|`\n- `%`, `/`, `*`\n- `-`, `+`\n- `==`, `!=`, `>`, `<`, `>=`, `<=`\n- `is`, `is not`\n- `and`\n- `or`\n\n### Limiting the size of the rendered output\n\nIn case you’re running Pebble with templates provided by someone else, there’s an attack similar to\n[zip bombs](https://en.wikipedia.org/wiki/Zip_bomb) or [XML bombs](https://en.wikipedia.org/wiki/Billion_laughs_attack)\nthat might cause your process to run out of memory. To protect against it, you can limit the size of the output when\nevaluating a template:\n```java\nPebbleEngine pebble = new PebbleEngine.Builder()\n                // Output should not exceed 10 MB.\n                .maxRenderedSize(10 * 1024 * 1024)\n                .build();\n```\n\nThis will throw a `PebbleException` when a template evaluation tries to write more characters than the limit you set.\n\n### IDE's plugin\nIf you want to add IDE's syntax highlighting, you can install this [plugin](https://plugins.jetbrains.com/idea/plugin/9407-pebble) for IntelliJ. Thank you to Bastien Jansen for his contribution.\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/guide/customize-defaults.md",
    "content": "---\n---\n\nPebble comes with a rich set of built-in tags and filters that will help you render your templates into  websites and other documents with ease. However, imagine a more specific use-case where the templates are not entirely under your control.\n\nIn these cases it might be advised to consider stripping-down Pebbles' built-in functionality that may otherwise introduce security-concers regarding the integrity and stability of your application.\n\n### Opt-Out using ExtensionCustomizer\n\nThe `ExtensionCustomizer` base class can be used to gain access to the default functionality before it is loaded into Pebbles template engine. Overwrite methods to get hold on provided default-functionality and modify whatever should be available for the template engine.\n\nThe following example removes the `ForTokenParser`, i.e. the ability to parse `{% for %}{{ ... }}{% endfor %}` constructs:\n\n```java\nclass ExampleOptOuts extends ExtensionCustomizer {\n\n  public ExampleOptOuts(Extension ext) {\n    super(ext);\n  }\n\n  @Override\n  public List<TokenParser> getTokenParsers() {\n    List<TokenParser> tokenParsers = Optional.ofNullable(super.getTokenParsers())\n                                        .map(ArrayList::new).orElseGet(ArrayList::new);\n      \n    tokenParsers.removeIf(x -> x instanceof ForTokenParser);\n    return tokenParsers;\n  }\n\n}\n```\n\nThe `ExtensionCustomizer` will be used to wrap any Pebble-extension which is provided by default. It can be registered in your setup code to create `PebbleEngine`:\n\n```java\nPebbleEngine engine = new PebbleEngine.Builder().registerExtensionCustomizer(ExampleOptOuts::new).build();\n```\n\n### Default implementation of ExtensionCustomizer\n\nThe `DisallowExtensionCustomizerBuilder` class can be used to disallow some default functionality, make pebble more controllable.\n\nFor example of use, see below:\n\n```java\nPebbleEngine engine = new PebbleEngine.Builder()\n        .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()\n                .disallowedTokenParserTags(singletonList(\"flush\"))\n                .disallowedFunctionKeys(singletonList(\"max\"))\n                .disallowedFilterKeys(singletonList(\"upper\"))\n                .disallowedTestKeys(singletonList(\"null\"))\n                .disallowedBinaryOperatorSymbols(singletonList(\">\"))\n                .disallowedUnaryOperatorSymbols(singletonList(\"-\"))\n                .build())\n        .build();\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/guide/escaping.md",
    "content": "---\n---\n\n# Escaping\n\n## Overview\n[XSS vulnerabilites](https://en.wikipedia.org/wiki/Cross-site_scripting) are the most common types of security\nvulnerabilities in web applications and in order to avoid them you must escape potentially unsafe data before\npresenting it to the end user. Pebble provides autoescaping of all such data which is enabled by default.\nAutoescaping can be turned off, in which case Pebble provides an {{ anchor('escape') }} filter for more\nfine-grained manual escaping.\n\n## Autoescaping\nAutoescaping, which is enabled by default, will automatically escape the outcome of expressions\ncontained within print delimiters, i.e. {% verbatim %}`{{` and `}}`{% endverbatim %}:\n```twig\n{% verbatim %}\n{% set danger = \"<br>\" %}\n{{ danger }}\n\n{# will output: &lt;br&gt; #}\n{%- endverbatim %}\n```\nThe {{ anchor('raw') }} filter can be used to prevent the autoescaper from escaping a particular expression. It is\nimportant that the raw filter is the last operation performed in the expression.\n```twig\n{% verbatim %}\n{% set danger = \"<br>\" %}\n{{ danger | raw }}\n\n{# will output: <br> #}\n{%- endverbatim %}\n```\nIf the raw filter is not the last operation performed within the expression, the expression will be deemed\nas possibly unsafe by the autoescaper and will be escaped. For example:\n```twig\n{% verbatim %}\n{% set danger = \"<br>\" %}\n{{ danger | raw | uppercase }}\n\n{# will output: &lt;BR&gt; #}\n{%- endverbatim %}\n```\n\n### Exceptions\nThere are a few exceptions where expressions are **not** automatically escaped:\n- If the expression only contains a string literal, it is assumed to be safe. For example:\n```twig\n{% verbatim %}\n{{ '<br>' }}\n\n{# will output: <br> #}\n{%- endverbatim %}\n```\n- The last operation contained within that expression is a filter or function that explicitly returns safe output. Such a filter or function would return an instance of `SafeString` instead of a regular String. The built-in filters that return safe markup include: `date`, `escape`, and `raw`. These filters must be the last operation performed within the expression in order for their output to be ignored by the autoescaper. For example:\n```twig\n{% verbatim %}\n{% set danger = \"<br>\" %}\n{{ danger | uppercase | raw }}\n\n{# will output: <br> #}\n{%- endverbatim %}\n```\n\n### Autoescape Tag\nThe {{ anchor('autoescape') }} tag can be used to temporarily disable/re-enable the autoescaper as well as\nchange the escaping strategy for a portion of the template.\n```twig\n{% verbatim %}\n{{ danger }} {# will be escaped by default #}\n{% autoescape false %}\n\t{{ danger }} {# will not be escaped #}\n{% endautoescape %}\n{%- endverbatim %}\n```\n```twig\n{% verbatim %}\n{{ danger }} {# will use the \"html\" escaping strategy #}\n{% autoescape \"js\" %}\n\t{{ danger }} {# will use the \"js\" escaping strategy #}\n{% endautoescape %}\n{%- endverbatim %}\n```\n\n### Disabling Autoescaper\n```java\nPebbleEngine engine = new PebbleEngine.Builder().autoEscaping(false).build();\n```\n\n## Manual Escaping\nIf autoescaping is disabled you can still use the {{ anchor('escape') }} filter to aid with manual escaping:\n```twig\n{% verbatim %}\n{% set danger = \"<br>\" %}\n{{ danger | escape }}\n\n{# will output: &lt;br&gt; #}\n{%- endverbatim %}\n```\n\n## Strategies\nWhen escaping data it is crucial that you utilize the correct escaping strategy depending on the context of the data.\nBy default, the autoescaper and the `escape` filter assume that you are escaping HTML data.\nI highly recommend reading the [OWASP Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)\nto understand the significance of escaping context.\n\nPebble provides the following escaping strategies:\n\n- html\n- js\n- css\n- url_param\n\nYou can use the {{ anchor('autoescape') }} tag to temporarily change the strategy used by the autoescaper otherwise you can\nchange the globally used default strategy:\n```java\nPebbleEngine engine = new PebbleEngine.Builder().defaultEscapingStrategy(\"js\").build();\n```\nThe escape filter will also accept a strategy as an argument:\n```js\n{% verbatim %}\nvar username =\"{{ user.name | escape(strategy=\"js\") }}\";\n{%- endverbatim %}\n```\n\n### Custom Strategy\nYou can add a custom escaping strategy by implementing `EscapingStrategy` and adding it to the `EscaperExtension`:\n```java\nPebbleEngine engine = new PebbleEngine.Builder().addEscapingStrategy(\"custom\", new CustomEscapingStrategy()).build();\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/guide/extending-pebble.md",
    "content": "---\n---\n\n# Extending Pebble\n\n## Overview\nPebble was designed to be flexible and accomodate the requirements of any project. You can add your own tags,\nfunctions, operators, filters, tests, and global variables. The majority of these are quite trivial to implement.\n\nBegin by creating a class that implements `Extension`. For your own convenience, I recommend extending\n`AbstractExtension` if you can. After implementing the required methods, register your extension with the `PebbleEngine`\nbefore compiling any templates:\n```java\nPebbleEngine engine = new PebbleEngine.Builder().extension(new CustomExtension()).build();\n```\n\n## Filters\nTo create custom filters, implement the `getFilters()` method of your extension which will return a map of filter\nnames and their corresponding implementations. A filter implementation must implement the `Filter` interface.\nThe\t`Filter` interface requires two methods to be implemented, `getArgumentNames()` and `apply()`. The\n`getArgumentNames()` method returns a list of Strings that define both the order and names of expected arguments. If \nthis method returns null or an empty list, the filter supports dynamic arguments. This means the user can pass any \nnumber of arguments—either named (e.g., myFilter(param=\"value\")) or positional (e.g., myFilter(\"value\"))—and they will be available in the arguments map.\n\nThe `apply` method is the actual filter implementation. Here's the parameters definition.\n\n| Parameter name | Description |\n| --- | --- |\n| input | the data to be filtered |\n| args | the map of arguments the user may have provided |\n| self | An instance of `PebbleTemplate` which can be used to retrieve the template name for example |\n| context | An instance of `EvaluationContext` which can be used to retrieve the locale for example| \n| lineNumber | Useful when throwing exception to provide line number | \n \nBecause Pebble is dynamically typed, you will have to downcast the arguments to the expected type.\nHere is an example of how the {{ anchor('upper') }} filter might be implemented:\n```java\npublic class UpperFilter implements Filter {\n\n\t@Override\n\tpublic List<String> getArgumentNames() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic Object apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber){\n\t\tif(input == null){\n\t\t\treturn null;\n\t\t}\n\t\tif (input instanceof String) {\n\t\t\treturn ((String) input).toUpperCase(context.getLocale());\n\t\t} else {\n\t\t\treturn input.toString().toUpperCase(context.getLocale());\n\t\t}\n\t}\n\n}\n```\n\n## Tests\nAdding custom tests is very similar to custom filters. Implement the `getTests()` method within your\nextension which will return a map of test names and their corresponding implementations. A test\nimplementation will implement the `Test` interface. The `Test` interface is exactly like the `Filter`\ninterface except the apply method returns a boolean instead of an arbitrary object of any type.\n\nHere is an example of how the {{ anchor('even') }} test might be implemented:\n```java\npublic class EvenTest implements Test {\n\n\t@Override\n\tpublic List<String> getArgumentNames() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic boolean apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber){\n\t\tif (input == null) {\n\t\t\tthrow new PebbleException(null, \"Can not pass null value to \\\"even\\\" test.\", lineNumber, self.getName());\n\t\t}\n    \n\t\tif (input instanceof Integer) {\n\t\t\treturn ((Integer) input) % 2 == 0;\n\t\t} else {\n\t\t\treturn ((Long) input) % 2 == 0;\n\t\t}\n\t}\n\n}\n```\n\n## Functions\nAdding functions is also very similar to custom filters. First and foremost, it's important to\nunderstand the different intentions behind a function and a filter because it can often be ambiguous\nwhich one should be implemented. A filter is intended to modify existing content where a function is\nmoreso intended to produce new content.\n\nTo add functions, implement the `getFunctions()` method within your extension which will return a map of function\nnames and their corresponding implementations. A function implementation will implement the `Function` interface.\nThe\t`Function` interface is very similar to the `Filter` and `Test` interfaces.\n\nHere is an example of how a fictional `fibonacciString` function might be implemented:\n```java\npublic class FibonnaciStringFunction implements Function {\n\n\t@Override\n\tpublic List<String> getArgumentNames() {\n\t\tList<String> names = new ArrayList<>();\n\t\tnames.add(\"length\");\n\t\treturn names;\n\t}\n\n\t@Override\n\tpublic Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) {\n\t\tInteger length = (Integer)args.get(\"length\");\n\t\tInteger prev1 = 0;\n\t\tInteger prev2 = 1;\n\n\t\tStringBuilder result = new StringBuilder();\n\n\t\tresult.append(\"01\");\n\n\t\tfor(int i = 2; i < length; i++){\n\t\t\tInteger next = prev1 + prev2;\n\t\t\tresult.append(next);\n\t\t\tprev1 = prev2;\n\t\t\tprev2 = next;\n\t\t}\n\t\treturn result.toString();\n\n\t}\n}\n```\n\n## Positional and Named Arguments\nFor filters, tests, and functions it is required that you implement the `getArgumentNames` method even if it\nreturns null. Returning a list of strings will allow the end user to call your filter/test/function using\nnamed arguments. If this method returns null or an empty list, the function supports dynamic arguments. This means \nthe user can pass any number of arguments—either named (e.g., myFunction(param=\"value\")) or positional \n(e.g., myFunction(\"value\"))—and they will be available in the arguments map.\n\nUsing the above fictional fibonacci function as an example, a user can invoke it in two different ways:\n```twig\n{% verbatim %}\n{{ fibonacci(10) }}\n{{ fibonacci(length=10) }}\n{%- endverbatim %}\n```\nIf the end user excludes the names and only uses positional arguments, the argument values will still end up\nbe mapped to the proper names when it's time to invoke the function's execute method. Your function implementation\ndoesn't have to worry whether the user used positional or named arguments. It is important though that if the\nfilter/function/test expects more than one argument, then the developer must communicate to the user the expected\norder of arguments in the chance that the user wants to invoke it without using names.\n\nSome functions such as the built in `min` and `max` functions accept an unlimited amount of arguments.\nFor this to happen, your function must not accept any named arguments (i.e. your `getArgumentNames` method\nwill return null or empty) and your `execute`` method will simply iterate over the values of the user provided\nargument map while ignoring the keys of that map (Pebble will use arbitrary keys if there are no names to map to).\n\n## Global Variables\nAdding global variables, which are variables that are accessbile to all templates, is very trivial.\nIn your custom extension, implement the `getGlobalVariables()` method which returns a `Map<String,Object>`.\nThe contents of this map will be merged into the context you provide to each template at the time of rendering.\n\n## Operators\nOperators are more complex to implement than filters or tests. To add custom operators, implement the\n`getBinaryOperators()` or the `getUnaryOperators()` method in your extension, or both. These methods return a\nlist of `BinaryOperator` or `UnaryOperator` objects, respectively.\n\nBinary operators require the following information:\n- Precedence: an integer relative to other operators which defines the order of operations.\n- Symbol: a String representing the actual operator. This is typically a single character but doesn't have to be.\n- Expression Class: A class that extends `BinaryExpression`. This class will perform the actual operator implementation.\n- Associativity: Either left or right depending on how the operator is used.\n\nA unary operator is much the same except it's expression class must extend `UnaryExpression` and there is no associativity.\n\nThe precedence values for existing core operators are as followed:\n- `or`: 10\n- `and`: 15\n- `is`: 20\n- `is not`: 20\n- `==`: 30\n- `!=`: 30\n- `>`: 30\n- `<`: 30\n- `>=`: 30\n- `<=`: 30\n- `+`: 40\n- `-`: 40\n- `not`: 50 (Unary)\n- `*`: 60\n- `/`: 60\n- `%`: 60\n- `|`: 100\n- `+`: 500 (Unary)\n- `-`: 500 (Unary)\n\nThe following is an\texample of how the addition operator (`+`) might have been implemented:\n```java\npublic class AdditionOperator implements BinaryOperator {\n\n\tpublic int getPrecedence(){\n\t\treturn 30;\n\t}\n\n\tpublic String getSymbol(){\n\t\treturn \"+\";\n\t}\n\n    public BinaryExpression<?> createInstance() {\n        return new AddExpression();\n    }\n\n    public BinaryOperatorType getType() {\n        return BinaryOperatorType.NORMAL;\n    }\n\n\tpublic Associativity getAssociativity(){\n\t\treturn Associativity.LEFT;\n\t}\n\n}\n```\nAlongside each operator class you will also need to implement a corresponding `BinaryExpression` class\nwhich actually implements the operator. The above example references a fictional `AdditionExpression` class\nwhich might look like the following:\n```java\npublic class AdditionExpression extends BinaryExpression<Object> {\n\n\t@Override\n\tpublic Object evaluate(PebbleTemplateImpl self, EvaluationContext context){\n\t\tInteger left = (Integer)getLeftExpression().evaluate(self, context);\n\t\tInteger right = (Integer)getRightExpression().evaluate(self, context);\n\n\t\treturn left + right;\n\t}\n\n}\n```\nIn the above example you will notice that children of BinaryExpression have access to two other\nexpressions, `leftExpression`, and `rightExpression`; these are the operands of your operator.\nPlease note that in the above example both operands are casted to Integers but in reality you can't\nalways make that assumption; the true addition expression is much more complex to handle different\ntypes of operands (Integers, Longs, Doubles, etc).\n\n## Tags\nCreating new tags is one of the most powerful abilities of Pebble. Your extension should start by\nimplementing the `getTokenParsers()` method. A `TokenParser` is responsible for converting all\nnecessary tokens to appropriate `RenderableNodes`. A token is a significant and irreducible\ngroup of characters found in a template (such as an operator, whitespace, variable name, delimiter, etc)\nand a `RenderableNode` is a Pebble class that is responsible for generating output.\n\nLet us look at an example of a `TokenParser`:\n```java\npublic class SetTokenParser implements TokenParser {\n\n\tpublic String getTag(){\n\t\treturn \"set\";\n\t}\n\n\t@Override\n\tpublic RenderableNode parse(Token token, Parser parser) {\n\t\tTokenStream stream = parser.getStream();\n\t\tint lineNumber = token.getLineNumber();\n\n\t\t// skip the \"set\" token\n\t\tstream.next();\n\n\t\t// use the built in expression parser to parse the variable name\n\t\tString name = parser.getExpressionParser().parseNewVariableName();\n\n\t\tstream.expect(Token.Type.PUNCTUATION, \"=\");\n\n\t\t// use the built in expression parser to parse the variable value\n\t\tExpression<?> value = parser.getExpressionParser().parseExpression();\n\n\t\t// expect to see \"%}\"\n\t\tstream.expect(Token.Type.EXECUTE_END);\n\n\t\t// NodeSet is composed of a name and a value\n\t\treturn new SetNode(lineNumber, name, value);\n\t}\n\n}\n```\nThe `getTag()` method must return the name of the tag. Pebble's main parser will use this name to determine\nwhen to delegate responsibility to your custom `TokenParser`. This example is parsing the `set` tag.\n\nThe parse method is invoked whenever the primary parser encounters a set token. This method should return\none `RenderableNode` instance which when rendered during the template evaluation, will write output to the\nprovided Writer object. If the `RenderableNode` contains children nodes, it should invoke the render method\nof those nodes as well.\n\nThe best way to learn all the details of parsing is to look at some of the tools used, as well as some examples.\nHere is a list of classes I suggest reading:\n- `TokenParser`\n- `Parser`\n- `SetTokenParser`\n- `ForTokenParser`\n- `IfNode`\n- `SetNode`\n\n## Attribute resolver (v3 only)\nTo create a new attribute resolver, implement the `getAttributeResolver()` method of your extension which will return a list of\nattribute resolvers to run. A attribute resolver implementation must implement the `AttributeResolver` interface.\nThe\t`AttributeResolver` interface requires one method to be implemented, `resolve()`.\n\nThe custom attribute resolver will be executed before all default pebble attribute resolvers. It replaces the\n`DynamicAttributeProvider` interface\n\n```java\npublic class DefaultAttributeResolver implements AttributeResolver {\n\n  @Override\n  public ResolvedAttribute resolve(Object instance,\n                                   Object attributeNameValue,\n                                   Object[] argumentValues,\n                                   boolean isStrictVariables,\n                                   String filename,\n                                   int lineNumber) {\n    if (instance instanceof CustomObject) {\n      return \"customValue\";\n    }\n    return null;\n  }\n}\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/guide/high-performance.md",
    "content": "---\n---\n\n# High Performance\n\n\n## Concurrency\nFirst and foremost, a `PebbleTemplate` object, once compiled, is completely thread safe. As long as the data backing\nthe template is also thread safe, you can render that single template instance using multiple threads at once.\n\nThe actual rendering of a template will typically occur in a sequential manner, from top to bottom. If, however,\nyou provide an `ExecutorService` to the `PebbleEngine` and make use of the {{ anchor('parallel') }} tag, you can\nhave multiple threads render different sections of your template at one time. This is especially useful if one section\nof your template is costly and will otherwise block the rendering of the rest of the template.\n\n## Streaming\nThe use of the {{ anchor('flush') }} tag can be used to stream the rendered output as it's being rendered.\nThis can significantly improve latency.\n\n## Performance Pitfalls\n- It is typically okay for a block to use the `flush` tag unless the contents of that block is being rendered using the {{ anchor('block') }} function. Typically the flush tag will flush to the `Writer` that you provided but the block function internally uses it's own `StringWriter` and therefore flushing will do no good."
  },
  {
    "path": "docs/src/orchid/resources/wiki/guide/installation.md",
    "content": "---\n---\n\n# Installation & Configuration\n## Installation\nPebble is hosted in the Maven Central Repository. Simply add the following dependency into your `pom.xml` file:\n```xml\n<dependency>\n\t<groupId>io.pebbletemplates</groupId>\n\t<artifactId>pebble</artifactId>\n\t<version>{{ site.version }}</version>\n</dependency>\n```\n\nAlso, snapshots of the master branch are deployed automatically with each successful commit. Instead of Maven Central, use the Sonatype snapshots repository at:\n```xml\n<url>https://oss.sonatype.org/content/repositories/snapshots</url>\n```\nYou can add the repository in your pom.xml\n\n```xml\n<repositories>\n  <repository>\n    <id>sonatype-public</id>\n    <name>Sonatype Public</name>\n    <url>https://oss.sonatype.org/content/groups/public</url>\n    <snapshots>\n      <enabled>true</enabled>\n    </snapshots>\n  </repository>\n</repositories>\n```\n\n## Set Up\nIf you are integrating Pebble with Spring MVC, read {{ anchor('this guide', 'Spring Integration') }}.\n\nYou will want to begin by creating a `PebbleEngine` which is responsible for coordinating the retrieval and\ncompilation of your templates:\n```java\nPebbleEngine engine = new PebbleEngine.Builder().build();\n```\nAnd now, with your new `PebbleEngine` instance you can start compiling templates:\n```java\nPebbleTemplate compiledTemplate = engine.getTemplate(\"templateName\");\n```\nFinally, simply provide your compiled template with a `Writer` object and a Map of variables to get your output!\n```java\nWriter writer = new StringWriter();\n\nMap<String, Object> context = new HashMap<>();\ncontext.put(\"name\", \"Mitchell\");\n\ncompiledTemplate.evaluate(writer, context);\n\nString output = writer.toString();\n```\n\n## Template Loader\nThe `PebbleEngineBuilder` will also accept a `Loader` implementation as an argument. A loader is responsible for\nfinding your templates.\n\nPebble ships with the following loader implementations:\n\n- `DelegatingLoader`: Delegates responsibility to a collection of children loaders.\n- `ClasspathLoader`: Uses a classloader to search the current classpath.\n- `FileLoader`:  Finds templates using a filesystem path. Must provide a mandatory absolute base path.\n- `ServletLoader`:  Uses a servlet context to find the template. This is the recommended loader for use within an\napplication server but is not enabled by default.\n- `Servlet5Loader`:  Same as `ServletLoader`, but for Jakarta Servlet 5.0 or newer.\n- `MemoryLoader`: Loader that supports inheritance and doesn't require a filesystem. This is useful for applications\n- `StringLoader`: Considers the name of the template to be the contents of the template. Should not be used in a production environment. It is primarily for testing and debugging. Many tags may not work when using this loader, such as \"extends\", \"imports\", etc.\n  that retrieve templates from a database for example.\n\nIf you do not provide a custom Loader, Pebble will use an instance of the `ClasspathLoader` by default.\n\n## Pebble Engine Settings\n\nAll the settings are set during the construction of the `PebbleEngine` object.\n\n| Setting  | Description | Default |\n| --- | --- | --- |\n| `cacheActive` | Flag to activate/desactivate template caching | true |\n| `templateCache` | An implementation of a ConcurrentMap cache that the Pebble engine will use to cache compiled templates. | Default implementation is `ConcurrentMapTemplateCache` and another implementation based on Caffeine is available (`CaffeineTemplateCache`) |\n| `tagCache` | An implementation of a ConcurrentMap cache that the Pebble engine will use for {{ anchor('cache tag', 'cache') }}. | Default implementation is `ConcurrentMapTagCache` and another implementation based on Caffeine is available (`CaffeineTagCache`) |\n| `defaultLocale` | The default locale which will be passed to each compiled template. The templates then use this locale for functions such as i18n, etc. A template can also be given a unique locale during evaluation.  | `Locale.getDefault()` |\n| `executorService` | An `ExecutorService` that allows the usage of some advanced multithreading features, such as the `parallel` tag. | `null` |\n| `loader` | An implementation of the `Loader` interface which is used to find templates. | An implementation of the `ClasspathLoader` |\n| `strictVariables` | If set to true, Pebble will throw an exception if you try to access a variable or attribute that does not exist (or an attribute of a null variable). If set to false, your template will treat non-existing variables/attributes as null without ever skipping a beat. | `false` |\n| `methodAccessValidator` | Pebble provides two implementations. NoOpMethodAccessValidator which do nothing and BlacklistMethodAccessValidator which checks that the method being called is not blacklisted. | `BlacklistMethodAccessValidator` \n| `literalDecimalTreatedAsInteger` | option for treating literal decimals as `int`. Otherwise it is `long`. | `false` |\n| `literalNumbersAsBigDecimals` | option for toggling to enable/disable literal numbers treated as BigDecimals | `false` |\n| `greedyMatchMethod` | option for toggling to enable/disable greedy matching mode for finding java method. Reduce the limit of the parameter type, try to find other method which has compatible parameter types. | `false` |\n| `maxRenderedSize` | option for limiting the size of the rendered output | `-1 (disabled)` |\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/guide/spring-boot-integration.md",
    "content": "---\n---\n\n# Pebble Spring Boot Starter\nSpring Boot starter for autoconfiguring Pebble.\n\n## Basic Usage\nAdd the starter dependency to your pom.xml:\n\n### spring-boot v4\n```XML\n<dependency>\n\t<groupId>io.pebbletemplates</groupId>\n\t<artifactId>pebble-spring-boot-starter</artifactId>\n\t<version>{{ site.version }}</version>\n</dependency>\n```\nOr build.gradle:\n```groovy\ncompile \"io.pebbletemplates:pebble-spring-boot-starter:{{ site.version }}\"\n```\n\n### spring-boot v3\n```XML\n<dependency>\n\t<groupId>io.pebbletemplates</groupId>\n\t<artifactId>pebble-legacy-spring-boot-starter</artifactId>\n\t<version>{{ site.version }}</version>\n</dependency>\n```\nOr build.gradle:\n```groovy\ncompile \"io.pebbletemplates:pebble-legacy-spring-boot-starter:{{ site.version }}\"\n```\n\nThis is enough for autoconfiguration to kick in. This includes:\n\n* a Loader that will pick template files ending in ``.peb`` from ``/templates/`` dir on the classpath\n* a PebbleEngine with default settings, configured with the previous loader\n* a Spring extension which offers some functionality described below\n* a ViewResolver that will output ``text/html`` in ``UTF-8``\n\nPLEASE NOTE: the starter depends on ``spring-boot-starter-webmvc`` or ``spring-boot-starter-webmvcflux`` but is marked\nas optional, you'll need to add the dependency yourself or configure Spring MVC appropriately.\n\n## Boot externalized configuration\n\nA number of properties can be defined in Spring Boot externalized configuration, eg. ``application.properties``,\nstarting with the prefix ``pebble``. See the\ncorresponding [PebbleProperties.java](https://github.com/PebbleTemplates/pebble/blob/master/pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleProperties.java)\nfor your starter version. Notable properties are:\n\n* ``pebble.prefix``: defines the prefix that will be prepended to the mvc view name. Defaults to ``/templates/``\n* ``pebble.suffix``: defines the suffix that will be appended to the mvc view name. Defaults to ``.peb``\n* ``pebble.charset``: defines the text encoding that will be used to configure the ViewResolver. Defaults to\n  ``UTF-8``\n* ``pebble.defaultLocale``: defines the default locale that will be used to configure the PebbleEngine. Defaults\n  to ``null``\n* ``pebble.strictVariables``: enable or disable the strict variable checking in the PebbleEngine. Defaults to\n  ``false``\n* ``pebble.greedyMatchMethod``: enable or disable the greedy matching mode for finding java method in the\n  PebbleEngine. Defaults to ``false``\n* ``pebble.servlet.cache``: enables or disables PebbleEngine caches. Defaults to ``true``\n* ``pebble.servlet.contentType``: defines the content type that will be used to configure the ViewResolver. Defaults to\n  ``text/html``\n* ``pebble.servlet.exposeRequestAttributes``: defines whether all request attributes should be added to the model prior\n  to merging with the template for the ViewResolver. Defaults to ``false``\n* ``pebble.servlet.exposeSessionAttributes``: defines whether all session attributes should be added to the model prior\n  to merging with the template for the ViewResolver. Defaults to ``false``\n* ``pebble.reactive.mediaTypes``: Configure the supported media types for Pebble views when used in a reactive Spring WebFlux application. This property allows you to specify a list of MediaType objects that the Pebble view resolver should consider when rendering templates. Defaults to ``null``\n\n## Examples\nThere is the spring petclinic example which has been migrated to [pebble](https://github.com/PebbleTemplates/spring-petclinic) \n\nThere is also a fully working example project located on [github](https://github.com/PebbleTemplates/pebble-example-spring)\nwhich can be used as a reference. It is a very simple and bare-bones project designed to only portray the basics.\nTo build the project, simply run `mvn install` and then deploy the resulting war file to a an application container.\n\n## Customizing Pebble\n### Pebble extensions\nExtensions defined as beans will be picked up and added to the PebbleEngine automatically:\n```java\n@Bean\npublic Extension myPebbleExtension1() {\n   return new MyPebbleExtension1();\n}\n\n@Bean\npublic Extension myPebbleExtension2() {\n   return new MyPebbleExtension2();\n}\n```\nCAVEAT: Spring will not gather all the beans if they're scattered across multiple @Configuration classes. If you use this mechanism, bundle all Extension @Beans in a single @Configuration class.\n\n### Customizing the Loader\nThe autoconfigurer looks for a bean named ``pebbleLoader`` in the context. You can define a custom loader with that name and it will be used to configure the default PebbleEngine:\n```java\n@Bean\npublic Loader<?> pebbleLoader() {\n   return new MyCustomLoader();\n}\n```\n**PLEASE NOTE**: this loader's prefix and suffix will be both overwritten when the ViewResolver is configured. You should use the externalized configuration for changing these properties.\n\n### Customizing the PebbleEngine\nLikewise, you can build a custom engine and make it the default by using the bean name ``pebbleEngine``:\n```java\n@Bean\npublic PebbleEngine pebbleEngine() {\n   return new PebbleEngine.Builder().build();\n}\n```\n\n### Customizing the MethodAccessValidator\nYou can provide your own MethodAccessValidator or switch to NoOpMethodAccessValidator by providing a MethodAccessValidator Bean\n```java\n@Bean\npublic MethodAccessValidator methodAccessValidator() {\n  return new NoOpMethodAccessValidator();\n}\n```\n\n### Customizing the ViewResolver\nAnd the same goes for the ViewResolver\n```java\n@Bean\npublic PebbleViewResolver pebbleViewResolver() {\n   return new PebbleViewResolver();\n}\n```\n\nFor reactive app\n```java\n@Bean\npublic PebbleReactiveViewResolver pebbleReactiveViewResolver() {\n   return new PebbleReactiveViewResolver(...)\n}\n```\n\nPLEASE NOTE: you need to change the Loader's prefix and suffix to match the custom ViewResolver's values.\n\n## Features\n### Access to Spring beans\nSpring beans are available to the template.\n```twig\n{% verbatim %}{{ beans.beanName }}{% endverbatim %}\n```\n\n### Access to http request\nHttpServletRequest object is available to the template.\n```twig\n{% verbatim %}{{ request.contextPath }}{% endverbatim %}\n```\n\n### Access to http response\nHttpServletResponse is available to the template.\n```twig\n{% verbatim %}{{ response.contentType }}{% endverbatim %}\n```\n\n### Access to http session\nHttpSession is available to the template.\n```twig\n{% verbatim %}{{ session.maxInactiveInterval }}{% endverbatim %}\n```\n\n## Spring extension\n\nThis extension has many functions for spring validation and the use of message bundle.\n\n### Href function\nFunction to automatically add the context path to a given url\n\n```twig\n{% verbatim %}<a href=\"{{ href('/foobar') }}\">Example</a>{% endverbatim %}\n```\n\n### Message function\nIt achieves the same thing as the i18n function, but instead, it uses the configured spring messageSource, typically the ResourceBundleMessageSource.\n\n```twig\n{% verbatim %}\nLabel = {{ message('label.test') }}\nLabel with params = {{ message('label.test.params', 'params1', 'params2') }}\n{%- endverbatim %}\n```\n\n### Spring validations and error messages\n6 validations methods and error messages are exposed using spring BindingResult. It needs as a parameter the form name and for a particular field, the field name.\n\nTo check if there's any error:\n```twig\n{% verbatim %}\n{{ hasErrors('formName') }}\n\n{{ hasGlobalErrors('formName') }}\n\n{{ hasFieldErrors('formName', 'fieldName') }}\n{%- endverbatim %}\n```\n\nTo output any error:\n```twig\n{% verbatim %}\n{% for err in getAllErrors('formName') %}\n    <p>{{ err }}</p>\n{% endfor %}\n\n{% for err in getGlobalErrors('formName') %}\n    <p>{{ err }}</p>\n{% endfor %}\n\n{% for err in getFieldErrors('formName', 'fieldName') %}\n    <p>{{ err }}</p>\n{% endfor %}\n{%- endverbatim %}\n```\n\n### Using Pebble for other tasks\nThe main role of this starter is to configure Pebble for generating MVC View results (the typical HTML). You may define more PebbleEngine/Loader beans for other usage patterns (like generating email bodies). Bear in mind that you should not reuse the default Loader for other Engine instances.\n\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/operator/comparisons.md",
    "content": "# Comparisons\nPebble provides the following comparison operators: `==`, `!=`, `<`, `>`, `<=`, `>=`. All of them except for `==`\nare equivalent to their Java counterparts. The `==` operator uses `java.util.Objects.equals(a, b)` behind the\nscenes to perform null safe value comparisons.\n\n> `equals` is an alias for `==`\n\n```twig\n{% if user.name equals \"Mitchell\" %}\n\t...\n{% endif %}\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/operator/contains.md",
    "content": "# `contains`\nThe `contains` operator can be used to determine if a collection, map, or array contains a particular item.\n```twig\n{% if [\"apple\", \"pear\", \"banana\"] contains \"apple\" %}\n\t...\n{% endif %}\n```\nWhen using maps, the contains operator checks for an existing key.\n```twig\n{% if {\"apple\":\"red\", \"banana\":\"yellow\"} contains \"banana\" %}\n\t...\n{% endif %}\n```\nThe operator can be used to look for multiple items at once:\n```twig\n{% if [\"apple\", \"pear\", \"banana\", \"peach\"] contains [\"apple\", \"peach\"] %}\n\t...\n{% endif %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/operator/is.md",
    "content": "---\n---\n\n# `is`\nThe `is` operator will apply a test to a variable which will return a boolean.\n\n```twig\n{% verbatim %}\n{% if 2 is even %}\n\t...\n{% endif %}\n{%- endverbatim %}\n```\nThe result can be negated using the {{ anchor('not', 'logic') }} operator."
  },
  {
    "path": "docs/src/orchid/resources/wiki/operator/logic.md",
    "content": "# Logic\n\nThe `and` operator and the `or` operator are available to join boolean expressions.\n```twig\n{% if 2 is even and 3 is odd %}\n\t...\n{% endif %}\n```\nThe `not` operator is available to negate a boolean expression.\n```twig\n{% if 3 is not even %}\n\t...\n{% endif %}\n```\nParenthesis can be used to group expressions to ensure a desired precedence.\n```twig\n{% if (3 is not even) and (2 is odd or 3 is even) %}\n\t...\n{% endif %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/operator/math.md",
    "content": "---\n---\n\n# Math\nAll the regular math operators are available for use. Order of operations applies.\n```twig\n{% verbatim %}\n{{ 2 + 2 / ( 10 % 3 ) * (8 - 1) }}\n{% endverbatim %}\n```\nThe result can be negated using the {{ anchor('not', 'logic') }} operator."
  },
  {
    "path": "docs/src/orchid/resources/wiki/operator/others.md",
    "content": "# Other Operators\nThe `|` operator is used to apply a filter to a variable.\n```twig\n{{ user.name | capitalize }}\n```\nPebble supports the use of the conditional operator (often named the ternary operator).\n```twig\n{{ foo == null ? bar : baz }}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/summary.md",
    "content": "## Guides\n- [Installation and Configuration](guide/installation.md)\n- [Spring Boot Integration](guide/spring-boot-integration.md)\n- [Spring petclinic](https://github.com/PebbleTemplates/spring-petclinic)\n- [Pebble Spring Example](https://github.com/PebbleTemplates/pebble-example-spring)\n- [Basic Usage](guide/basic-usage.md)\n- [Customize Defaults](guide/customize-defaults.md)\n- [Escaping](guide/escaping.md)\n- [Extending Pebble](guide/extending-pebble.md)\n- [High Performance Techniques](guide/high-performance.md)\n\n### Tags\n- [autoescape](tag/autoescape.md)\n- [block](tag/block.md)\n- [cache](tag/cache.md)\n- [embed](tag/embed.md)\n- [extends](tag/extends.md)\n- [filter](tag/filter.md)\n- [flush](tag/flush.md)\n- [for](tag/for.md)\n- [from](tag/from.md)\n- [if](tag/if.md)\n- [import](tag/import.md)\n- [include](tag/include.md)\n- [macro](tag/macro.md)\n- [parallel](tag/parallel.md)\n- [set](tag/set.md)\n- [verbatim](tag/verbatim.md)\n\n## Filters\n- [abbreviate](filter/abbreviate.md)\n- [abs](filter/abs.md)\n- [base64decode](filter/base64decode.md)\n- [base64encode](filter/base64encode.md)\n- [capitalize](filter/capitalize.md)\n- [date](filter/date.md)\n- [default](filter/default.md)\n- [escape](filter/escape.md)\n- [first](filter/first.md)\n- [join](filter/join.md)\n- [last](filter/last.md)\n- [length](filter/length.md)\n- [lower](filter/lower.md)\n- [numberformat](filter/numberformat.md)\n- [raw](filter/raw.md)\n- [replace](filter/replace.md)\n- [reverse](filter/reverse.md)\n- [rsort](filter/rsort.md)\n- [sha256](filter/sha256.md)\n- [slice](filter/slice.md)\n- [sort](filter/sort.md)\n- [split](filter/split.md)\n- [title](filter/title.md)\n- [trim](filter/trim.md)\n- [upper](filter/upper.md)\n- [urlencode](filter/urlencode.md)\n\n## Functions\n- [block](function/blockFunction.md)\n- [i18n](function/i18n.md)\n- [max](function/max.md)\n- [min](function/min.md)\n- [parent](function/parent.md)\n- [range](function/range.md)\n\n## Tests\n- [empty](test/empty.md)\n- [even](test/even.md)\n- [map](test/map.md)\n- [null](test/null.md)\n- [odd](test/odd.md)\n- [iterable](test/iterable.md)\n\n## Operators\n- [comparisons](operator/comparisons.md) (`==`, `!=`, `<`, `>`, `<=`, `>=`, `equals`)\n- [contains](operator/contains.md) (`contains`)\n- [is](operator/is.md)\n- [logic](operator/logic.md) (`and`, `or`, `not`, `()`)\n- [math](operator/math.md) (`+`, `-`, `/`, `%`, `*`)\n- [others](operator/others.md) (`|`, `?:`)\n\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/autoescape.md",
    "content": "---\n---\n\n# `autoescape`\n\nThe `autoescape` tag can be used to temporarily disable/re-enable the autoescaper as well as change the\nescaping strategy for a portion of the template.\n```twig\n{% verbatim %}\n{{ danger }} {# will be escaped by default #}\n{% autoescape false %}\n\t{{ danger }} {# will not be escaped #}\n{% endautoescape %}\n{%- endverbatim %}\n```\n```twig\n{% verbatim %}\n{{ danger }} {# will use the \"html\" escaping strategy #}\n{% autoescape \"js\" %}\n\t{{ danger }} {# will use the \"js\" escaping strategy #}\n{% endautoescape %}\n{%- endverbatim %}\n```\nPlease read the {{ anchor('escaping guide', 'Escaping') }} for more information about escaping."
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/block.md",
    "content": "---\n---\n\n# `block`\nThe `block` tag performs two functions. If used in a parent template, it will designate a section as being\nallowed to be overriden by a child template. If used in a child template, it will override the content\noriginally declared in the parent template. See the\t{{ anchor('extends') }} tag for a more detailed explanation on how\nto implement template inheritance.\n\nThe contents of a block will only be used if a child template does not override it. It is often useful to\ndefine empty blocks as placeholders for content to be provided by a child template.\n\nThe\t`block` tag is immediately followed by the name of the block. This name will be the same name\nthe child template uses to override it. The `endblock` tag can optionally contain the block's name for readability.\n\nIn the following example we create a block with the name 'header':\n```twig\n{% verbatim %}\n{% block header %}\n\t<h1> Introduction </h1>\n{% endblock header %}\n{%- endverbatim %}\n```\nA child template should not have any content outside of blocks. A child template is only used to override\nblocks of a parent template."
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/cache.md",
    "content": "# `cache`\n\nCache the rendering portion of a page. Cache name can be an expression or a static string. It uses the cache name and the locale as a key in the cache.\n\nIn the following example we create a cache with the name 'menu':\n```twig\n{% cache 'menu' %}\n    {% for item in items %}\n        {{ item.text }}\n        ....\n    {% endfor %}\n{% endcache %}\n```\n\nCache implementation can be overriden with the PebbleEngine Builder.\n```java\n return new PebbleEngine.Builder()\n                .loader(this.templateLoader())\n                .tagCache(CacheBuilder.newBuilder().maximumSize(200).build())\n                .build();\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/embed.md",
    "content": "---\n---\n\n# `embed`\n\nThe `embed` tag allows you to insert the rendered output of another template directly into the current template, while \noverriding some of its blocks. It effectively combines the behavior of {{anchor('include')}} with that of \n{{anchor('extends')}} for creating reusable, yet flexible, template fragments, or for composing micro-layouts.\n\n{% verbatim %}\n\nFor example, imagine building a template `card.peb` as a reusable component in your layout. All cards should have the \nsame markup, but the content can change drastically throughout your site. `card.peb` might then look like:\n\n```twig\n// card.peb\n<div class=\"card\">\n    {% block cardContent %}\n    {% endblock %}\n</div>\n```\n\nNow, you can include that template elsewhere in your layout, and override the `cardContent` block to \"inject\" rich \ncontent into that template at the call-side. For example, you may want to display a grid of your store's most popular \nproducts as cards, with the last card linking to the full catalog. Embedding `card.peb` and overriding the `cardContent`\nblock ensures that the markup for both types of cards are always the same, even though what's displayed on each card is \nquite different.\n\n```twig\n// layout.peb\n\n{% for product in popularProducts %}\n    {% embed 'card.peb' %}\n        {% block cardContent %}\n            <h1>{{ product.name }}</h1>\n            <p>{{ product.description }}</p>\n        {% endblock %}\n    {% endembed %}\n{% endfor %}\n\n{% embed 'card.peb' %}\n    {% block cardContent %}\n        <a href=\"...\">See all 100+ products</a>\n    {% endblock %}\n{% endembed %}\n```\n\nEmbeds can be used multiple times in the same template, and may also be used in a template that itself extends another.\nEach template will then maintain its own block hierarchy. In other words, block overridden within the body of the \n`embed` tag will not accidentally override those defined in the main template, and likewise blocks defined in the main\ntemplate or its parent templates will not get mixed with those in the embedded template or its parent templates. \n\n```twig\n// main.peb\n{% extends 'base.peb' %}\n\n{% block mainContent %}\n    {{ parent() }} {# renders mainContent block from base.peb #}\n    {{ block('footer') }} {# renders footer block from base.peb, the global page footer #}\n    \n    {% embed 'card.peb' %}\n        {% block mainContent %}\n            {{ parent() }} {# renders mainContent block from card.peb #}\n            {{ block('footer') }} {# renders footer block from card.peb, the card footer (not the global page footer) #}\n        {% endblock %}\n    {% endembed %}\n{% endblock %}\n```\n\n## Scope\n\nEmbedded templates will have access to the same variables that the current template does.\n\n```twig\nTop Content\n{% embed \"advertisement\" %}{% endembed %}\nBottom Content\n{% embed \"footer\" %}{% endembed %}\n```\n\nYou can add additional variables to the context of the embedded template by passing a map after the `with` keyword. The embedded template will have access to the same variables that the current template does plus the additional ones defined in the map passed after the `with` keyword:\n\n```twig\n{% embed \"advertisement\" with {\"foo\":\"bar\"} %}\n    {% block title %}\n        Ad with title\n    {% endblock %}\n    {% block content %}\n        Ad with title\n    {% endblock %}\n{% endembed %}\n```\n\n## Dynamic embed\nThe `embed` tag will accept an expression to determine the template to embed at runtime. For example:\n```twig\n{% embed admin ? 'adminFooter' : 'defaultFooter' %}\n{% endembed %}\n```\n{% endverbatim %}\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/extends.md",
    "content": "# `extends`\nThe `extends` tag is used to declare a parent template. It should be the very first tag used in a child\ntemplate and a child template can only extend up to one parent template.\n\nThe best way to understand template inheritance is to study an example. Let us look at a parent\ntemplate called \"base\":\n```twig\n<html>\n\t<head>\n\t\t<title>{% block title %} {% endblock %}</title>\n\t</head>\n\t<body>\n\t\t<div id=\"content\">\n\t\t\t{% block content %}\n\t\t\t\tDefault content goes here.\n\t\t\t{% endblock %}\n\t\t</div>\n\n\t\t<div id=\"footer\">\n\t\t\t{% block footer %}\n\t\t\t\tDefault footer content\n\t\t\t{% endblock %}\n\t\t</div>\n\t</body>\n</html>\n```\nAnd now let's look at a child template called \"home\" which extends \"base\":\n```twig\n{% extends \"base\" %}\n\n{% block title %} Home {% endblock %}\n\n{% block content %}\n\tHome page content.\n{% endblock %}\n```\nAnd finally let's look at the resulting output after evaluating \"home\":\n```twig\n<html>\n\t<head>\n\t\t<title> Home </title>\n\t</head>\n\t<body>\n\t\t<div id=\"content\">\n\t\t\tHome page content will override the default content.\n\t\t</div>\n\n\t\t<div id=\"footer\">\n\t\t\tDefault footer content\n\t\t</div>\n\t</body>\n</html>\n```\nTo summarize, parent templates define blocks and child templates will override the contents of those blocks.\nIf a child template does not override the content of a particular block, the content provided by the parent\ntemplate will be used.\n\nThere is no limit to how long of an inheritance chain that you can create; i.e. a child template can\nitself have a child template. A lot of potential comes from this fact because you can create a hierarchy of\ntemplates to minimize how much content you have to write on the lower levels.\n\n## Dynamic Inheritance\nThe `extends` tag will accept an expression to determine the parent template at runtime. For example:\n```twig\n{% extends ajax ? 'ajax' : 'base' %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/filter.md",
    "content": "# `filter`\n\nThe `filter` tag allows you to apply a filter to a large chunk of template.\n```twig\n{% filter upper %}\n\thello\n{% endfilter %}}\n\n{# output: 'HELLO' #}\n```\nMultiple filters can be chained together.\n```twig\n{% filter upper | escape %}\n\thello<br>\n{% endfilter %}}\n\n{# output: 'HELLO&lt;br&gt;' #}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/flush.md",
    "content": "# `flush`\nThe `flush` tag allows you to flush all currently rendered output to the provided `Writer`.\n\n```twig\n{{ headerText }}\n{% flush %}\n{{ content }}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/for.md",
    "content": "# `for`\n\nThe `for` tag is used to iterate through primitive arrays or anything that implements the `java.lang.Iterable`\ninterface, as well as maps.\n```twig\n{% for user in users %}\n\t{{ user.name }} lives in {{ user.city }}.\n{% endfor %}\n```\nWhile inside of the loop, Pebble provides a couple of special variables to help you out:\n- loop.index - a zero-based index that increments with every iteration.\n- loop.length - the size of the object we are iterating over.\n- loop.first - True if first iteration\n- loop.last - True if last iteration\n- loop.revindex - The number of iterations from the end of the loop\n\n```twig\n{% for user in users %}\n\t{{ loop.index }} - {{ user.id }}\n{% endfor %}\n```\nThe `for` tag also provides a convenient way to check if the iterable object is empty with the included `else` tag.\n```twig\n{% for user in users %}\n\t{{ loop.index }} - {{ user.id }}\n{% else %}\n\tThere are no users to display.\n{% endfor %}\n```\nIterating over maps can be done like so:\n```twig\n{% for entry in map %}\n    {{ entry.key }} - {{ entry.value }}\n{% endfor %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/from.md",
    "content": "---\n---\n\n# `from`\nThe from tag imports {{ anchor('macro') }} names into the current namespace. The tag is documented in detail in the documentation for the {{ anchor('import') }} tag.\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/if.md",
    "content": "---\n---\n\n# `if`\nThe `if` tag allows you to designate a chunk of content as conditional depending on the result of an expression\n```twig\n{% verbatim %}\n{% if users is empty %}\n\tThere are no users.\n{% elseif users|length == 1 %}\n\tThere is only one user.\n{% else %}\n\tThere are many users.\n{% endif %}\n{%- endverbatim %}\n```\nThe expression used in the `if` statement often makes use of the {{ anchor('is') }} operator.\n\n### Supported conditions\n\n`If` tag currently supports the following expression\n\n| Value  | Boolean expression |\n| --- | --- |\n| boolean | boolean value |\n| Empty string | false |\n| Non empty string | true |\n| numeric zero | false |\n| numeric different than zero | true |\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/import.md",
    "content": "---\n---\n\n# `import`\nThe `import` tag allows you to use {{ anchor('macros', 'macro') }} defined in another template.\n\nAssuming that a macro named `input` exists in a template called `form_util` you can import it like so:\n\n```twig\n{% verbatim %}\n{% import \"form_util\" %}\n\n{{ input(\"text\", \"name\", \"Mitchell\") }}\n{%- endverbatim %}\n```\n\nThe easiest and most flexible is importing the whole module into a variable. That way you can access the attributes:\n\n```twig\n{% verbatim %}\n{% import 'forms.html' as forms %}\n\n<dl>\n    <dt>Username</dt>\n    <dd>{{ forms.input('username') }}</dd>\n    <dt>Password</dt>\n    <dd>{{ forms.input('password', null, 'password') }}</dd>\n</dl>\n<p>{{ forms.textarea('comment') }}</p>\n{%- endverbatim %}\n```\n\nAlternatively you can import names from the template into the current namespace:\n\n```twig\n{% verbatim %}\n{% from 'forms.html' import input as input_field, textarea %}\n\n<dl>\n    <dt>Username</dt>\n    <dd>{{ input_field('username') }}</dd>\n    <dt>Password</dt>\n    <dd>{{ input_field('password', '', 'password') }}</dd>\n</dl>\n<p>{{ textarea('comment') }}</p>\n{%- endverbatim %}\n```\n\n## Dynamic Import\nThe `import` tag will accept an expression to determine the template to import at runtime. For example:\n```twig\n{% verbatim %}\n{% import modern ? 'ajax_form_util' : 'simple_form_util' %}\n\n{{ input(\"text\", \"name\", \"Mitchell\") }}\n{%- endverbatim %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/include.md",
    "content": "# `include`\n\nThe `include` tag allows you to insert the rendered output of another template directly into the\ncurrent template. The included template will have access to the same variables that the current template does.\n\n```twig\nTop Content\n{% include \"advertisement\" %}\nBottom Content\n{% include \"footer\" %}\n```\n\nYou can add additional variables to the context of the included template by passing a map after the `with` keyword. The included template will have access to the same variables that the current template does plus the additional ones defined in the map passed after the `with` keyword:\n\n```twig\n{% include \"advertisement\" with {\"foo\":\"bar\"} %}\n```\n\n## Dynamic Include\nThe `include` tag will accept an expression to determine the template to include at runtime. For example:\n```twig\n{% include admin ? 'adminFooter' : 'defaultFooter' %}\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/macro.md",
    "content": "---\n---\n\n# `macro`\nThe `macro` tag allows you to create a chunk of reusable and dynamic content. The macro can be called\nmultiple times in the current template or even from another template with the help of the {{ anchor('import') }} tag.\n\nIt doesn't matter where in the current template you define a macro, i.e. whether it's before or after you call it.\nHere is an example of how to define a macro:\n```twig\n{% verbatim %}\n{% macro input(type=\"text\", name, value) %}\n\t<input type=\"{{ type }}\" name=\"{{ name }}\" value=\"{{ value }}\" />\n{% endmacro %}\n{%- endverbatim %}\n```\nAnd now the macro can be called numerous times throughout the template, like so:\n```twig\n{% verbatim %}\n{{ input(name=\"country\") }}\n{# will output: <input type=\"text\" name=\"country\" value=\"\" /> #}\n{%- endverbatim %}\n```\nIf the macro resides in another template, use the {{ anchor('import') }} tag first.\n```twig\n{% verbatim %}\n{% import \"form_util\" %}\n{{ input(\"text\", \"country\", \"Canada\") }}\n{%- endverbatim %}\n```\nA macro does not have access to the same variables that the rest of the template has access to.\nA macro can only work with the variables provided as arguments.\n\n### Access to the global context\nYou can pass the whole context as an argument by using the special `_context` variable if you need to access\nvariables outside of the macro scope:\n```twig\n{% verbatim %}\n{% set foo = 'bar' %}\n\n{{ test(_context) }}\n{% macro test(_context) %}\n\t{{ _context.foo }}\n{% endmacro %}\n\n{# will output: bar #}\n{%- endverbatim %}\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/parallel.md",
    "content": "---\n---\n\n# `parallel`\nThe `parallel` tag allows you to designate a chunk of content to be rendered using a new thread.\nThis tag is only available if you provide an `ExecutorService` to the main `PebbleEngine`.\n\n```twig\n{% verbatim %}\n{{ upperContent }}\n\n{% parallel %}\n    {{ calculation.slowCalculation }}\n{% endparallel %}\n\n{{ lowerContent }}\n{% endverbatim %}\n```\nIn the above example, the slow calculation will not block the `lowerContent` from being evaluated concurrently.\n\nSee the {{ anchor('high performance guide', 'High Performance Techniques') }} for more tips on how to improve performance.\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/set.md",
    "content": "# `set`\nThe `set` tag allows you to define a variable in the current context, whether it currently exists or not.\n```twig\n{% set header = \"Test Page\" %}\n\n{{ header }}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/tag/verbatim.md",
    "content": "# `verbatim`\nThe `verbatim` tag allows you to write a block of Pebble syntax that won't be parsed.\n```twig\n{% verbatim %}\n\t{% for user in users %}\n\t\t{{ user.name }}\n\t{% endfor %}\n{% endverbatim %}\n```\n<br/>\n## Inline Verbatim Text\n\nFor inline verbatim text, a string literal can be used. For example, if you need to include **{{** in the output of a template, you can use `{{ \"{{\" }}` in string literal in the Pebble template \n\nThis would be useful if you are using Pebble to generate Angular HTML component template files:\n\n```javascript\n<td>{{ \"{{\" }}school.name{{ \"}}\" }}</td>\n```\n\nwould produce the following template output:\n\n```javascript\n<td>{{school.name}}</td>\n```\n"
  },
  {
    "path": "docs/src/orchid/resources/wiki/test/empty.md",
    "content": "# `empty`\nThe `empty` test checks if a variable is empty. A variable is empty if it is null, an empty string, an\nempty collection, or an empty map.\n```twig\n{% if user.email is empty %}\n\t...\n{% endif %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/test/even.md",
    "content": "# `even`\nThe `even` test checks if an integer is even.\n```twig\n{% if 2 is even %}\n\t...\n{% endif %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/test/iterable.md",
    "content": "# `iterable`\nThe `iterable` test checks if a variable implements `java.lang.Iterable`.\n```twig\n{% if users is iterable %}\n\t{% for user in users %}\n\t\t...\n\t{% endfor %}\n{% endif %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/test/map.md",
    "content": "# `map`\nThe `map` test checks if a variable is an instance of a map.\n```twig\n{% if {\"apple\":\"red\", \"banana\":\"yellow\"} is map %}\n\t...\n{% endif %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/test/null.md",
    "content": "# `null`\n\nThe `null` test checks if a variable is null.\n```twig\n{% if user.email is null %}\n\t...\n{% endif %}\n```"
  },
  {
    "path": "docs/src/orchid/resources/wiki/test/odd.md",
    "content": "# `odd`\nThe `odd` test checks if an integer is odd.\n```twig\n{% if 3 is odd %}\n\t...\n{% endif %}\n```"
  },
  {
    "path": "intellij-java-google-style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<code_scheme name=\"GoogleStyle\">\n  <option name=\"OTHER_INDENT_OPTIONS\">\n    <value>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n      <option name=\"USE_TAB_CHARACTER\" value=\"false\"/>\n      <option name=\"SMART_TABS\" value=\"false\"/>\n      <option name=\"LABEL_INDENT_SIZE\" value=\"0\"/>\n      <option name=\"LABEL_INDENT_ABSOLUTE\" value=\"false\"/>\n      <option name=\"USE_RELATIVE_INDENTS\" value=\"false\"/>\n    </value>\n  </option>\n  <option name=\"INSERT_INNER_CLASS_IMPORTS\" value=\"true\"/>\n  <option name=\"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\"/>\n  <option name=\"NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\"/>\n  <option name=\"PACKAGES_TO_USE_IMPORT_ON_DEMAND\">\n    <value/>\n  </option>\n  <option name=\"IMPORT_LAYOUT_TABLE\">\n    <value>\n      <package name=\"\" withSubpackages=\"true\" static=\"true\"/>\n      <emptyLine/>\n      <package name=\"\" withSubpackages=\"true\" static=\"false\"/>\n    </value>\n  </option>\n  <option name=\"RIGHT_MARGIN\" value=\"100\"/>\n  <option name=\"JD_ALIGN_PARAM_COMMENTS\" value=\"false\"/>\n  <option name=\"JD_ALIGN_EXCEPTION_COMMENTS\" value=\"false\"/>\n  <option name=\"JD_P_AT_EMPTY_LINES\" value=\"false\"/>\n  <option name=\"JD_KEEP_EMPTY_PARAMETER\" value=\"false\"/>\n  <option name=\"JD_KEEP_EMPTY_EXCEPTION\" value=\"false\"/>\n  <option name=\"JD_KEEP_EMPTY_RETURN\" value=\"false\"/>\n  <option name=\"KEEP_CONTROL_STATEMENT_IN_ONE_LINE\" value=\"false\"/>\n  <option name=\"KEEP_BLANK_LINES_BEFORE_RBRACE\" value=\"0\"/>\n  <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\"/>\n  <option name=\"BLANK_LINES_AFTER_CLASS_HEADER\" value=\"0\"/>\n  <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\"/>\n  <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\"/>\n  <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\"/>\n  <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\"/>\n  <option name=\"EXTENDS_LIST_WRAP\" value=\"1\"/>\n  <option name=\"THROWS_KEYWORD_WRAP\" value=\"1\"/>\n  <option name=\"METHOD_CALL_CHAIN_WRAP\" value=\"1\"/>\n  <option name=\"BINARY_OPERATION_WRAP\" value=\"1\"/>\n  <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\"/>\n  <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\"/>\n  <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\"/>\n  <option name=\"FOR_STATEMENT_WRAP\" value=\"1\"/>\n  <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\"/>\n  <option name=\"WRAP_COMMENTS\" value=\"true\"/>\n  <option name=\"IF_BRACE_FORCE\" value=\"3\"/>\n  <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\"/>\n  <option name=\"WHILE_BRACE_FORCE\" value=\"3\"/>\n  <option name=\"FOR_BRACE_FORCE\" value=\"3\"/>\n  <option name=\"SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE\" value=\"true\"/>\n  <AndroidXmlCodeStyleSettings>\n    <option name=\"USE_CUSTOM_SETTINGS\" value=\"true\"/>\n    <option name=\"LAYOUT_SETTINGS\">\n      <value>\n        <option name=\"INSERT_BLANK_LINE_BEFORE_TAG\" value=\"false\"/>\n      </value>\n    </option>\n  </AndroidXmlCodeStyleSettings>\n  <JSCodeStyleSettings>\n    <option name=\"INDENT_CHAINED_CALLS\" value=\"false\"/>\n  </JSCodeStyleSettings>\n  <Python>\n    <option name=\"USE_CONTINUATION_INDENT_FOR_ARGUMENTS\" value=\"true\"/>\n  </Python>\n  <TypeScriptCodeStyleSettings>\n    <option name=\"INDENT_CHAINED_CALLS\" value=\"false\"/>\n  </TypeScriptCodeStyleSettings>\n  <XML>\n    <option name=\"XML_ALIGN_ATTRIBUTES\" value=\"false\"/>\n    <option name=\"XML_LEGACY_SETTINGS_IMPORTED\" value=\"true\"/>\n  </XML>\n  <codeStyleSettings language=\"CSS\">\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"ECMA Script Level 4\">\n    <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\"/>\n    <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\"/>\n    <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\"/>\n    <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\"/>\n    <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\"/>\n    <option name=\"EXTENDS_LIST_WRAP\" value=\"1\"/>\n    <option name=\"BINARY_OPERATION_WRAP\" value=\"1\"/>\n    <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\"/>\n    <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\"/>\n    <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\"/>\n    <option name=\"FOR_STATEMENT_WRAP\" value=\"1\"/>\n    <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\"/>\n    <option name=\"IF_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"WHILE_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"FOR_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\"/>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"HTML\">\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"JAVA\">\n    <option name=\"KEEP_CONTROL_STATEMENT_IN_ONE_LINE\" value=\"false\"/>\n    <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\"/>\n    <option name=\"BLANK_LINES_AFTER_CLASS_HEADER\" value=\"1\"/>\n    <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\"/>\n    <option name=\"ALIGN_MULTILINE_RESOURCES\" value=\"false\"/>\n    <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\"/>\n    <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\"/>\n    <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\"/>\n    <option name=\"EXTENDS_LIST_WRAP\" value=\"1\"/>\n    <option name=\"THROWS_KEYWORD_WRAP\" value=\"1\"/>\n    <option name=\"METHOD_CALL_CHAIN_WRAP\" value=\"1\"/>\n    <option name=\"BINARY_OPERATION_WRAP\" value=\"1\"/>\n    <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\"/>\n    <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\"/>\n    <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\"/>\n    <option name=\"FOR_STATEMENT_WRAP\" value=\"1\"/>\n    <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\"/>\n    <option name=\"WRAP_COMMENTS\" value=\"true\"/>\n    <option name=\"IF_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"WHILE_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"FOR_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\"/>\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"JSON\">\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"JavaScript\">\n    <option name=\"RIGHT_MARGIN\" value=\"80\"/>\n    <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\"/>\n    <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\"/>\n    <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\"/>\n    <option name=\"CALL_PARAMETERS_WRAP\" value=\"1\"/>\n    <option name=\"METHOD_PARAMETERS_WRAP\" value=\"1\"/>\n    <option name=\"BINARY_OPERATION_WRAP\" value=\"1\"/>\n    <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\"/>\n    <option name=\"TERNARY_OPERATION_WRAP\" value=\"1\"/>\n    <option name=\"TERNARY_OPERATION_SIGNS_ON_NEXT_LINE\" value=\"true\"/>\n    <option name=\"FOR_STATEMENT_WRAP\" value=\"1\"/>\n    <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"1\"/>\n    <option name=\"IF_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"WHILE_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"FOR_BRACE_FORCE\" value=\"3\"/>\n    <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\"/>\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"PROTO\">\n    <option name=\"RIGHT_MARGIN\" value=\"80\"/>\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"protobuf\">\n    <option name=\"RIGHT_MARGIN\" value=\"80\"/>\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"Python\">\n    <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\"/>\n    <option name=\"RIGHT_MARGIN\" value=\"80\"/>\n    <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\"/>\n    <option name=\"PARENT_SETTINGS_INSTALLED\" value=\"true\"/>\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"SASS\">\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"SCSS\">\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"TypeScript\">\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"XML\">\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"2\"/>\n      <option name=\"TAB_SIZE\" value=\"2\"/>\n    </indentOptions>\n    <arrangement>\n      <rules>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>xmlns:android</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>xmlns:.*</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:id</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>style</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>^$</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:.*Style</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_width</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_height</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_weight</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_margin</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_marginTop</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_marginBottom</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_marginStart</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_marginEnd</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_marginLeft</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_marginRight</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:layout_.*</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:padding</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:paddingTop</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:paddingBottom</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:paddingStart</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:paddingEnd</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:paddingLeft</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*:paddingRight</NAME>\n                <XML_ATTRIBUTE/>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_NAMESPACE>http://schemas.android.com/apk/res-auto</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_NAMESPACE>http://schemas.android.com/tools</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <NAME>.*</NAME>\n                <XML_NAMESPACE>.*</XML_NAMESPACE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n      </rules>\n    </arrangement>\n  </codeStyleSettings>\n  <Objective-C>\n    <option name=\"INDENT_NAMESPACE_MEMBERS\" value=\"0\"/>\n    <option name=\"INDENT_C_STRUCT_MEMBERS\" value=\"2\"/>\n    <option name=\"INDENT_CLASS_MEMBERS\" value=\"2\"/>\n    <option name=\"INDENT_VISIBILITY_KEYWORDS\" value=\"1\"/>\n    <option name=\"INDENT_INSIDE_CODE_BLOCK\" value=\"2\"/>\n    <option name=\"KEEP_STRUCTURES_IN_ONE_LINE\" value=\"true\"/>\n    <option name=\"FUNCTION_PARAMETERS_WRAP\" value=\"5\"/>\n    <option name=\"FUNCTION_CALL_ARGUMENTS_WRAP\" value=\"5\"/>\n    <option name=\"TEMPLATE_CALL_ARGUMENTS_WRAP\" value=\"5\"/>\n    <option name=\"TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE\" value=\"true\"/>\n    <option name=\"ALIGN_INIT_LIST_IN_COLUMNS\" value=\"false\"/>\n    <option name=\"SPACE_BEFORE_SUPERCLASS_COLON\" value=\"false\"/>\n  </Objective-C>\n  <Objective-C-extensions>\n    <option name=\"GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES\" value=\"ASK\"/>\n    <option name=\"RELEASE_STYLE\" value=\"IVAR\"/>\n    <option name=\"TYPE_QUALIFIERS_PLACEMENT\" value=\"BEFORE\"/>\n    <file>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Import\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Macro\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Typedef\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Enum\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Constant\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Global\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Struct\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"FunctionPredecl\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Function\"/>\n    </file>\n    <class>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Property\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Synthesize\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InitMethod\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"StaticMethod\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InstanceMethod\"/>\n      <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"DeallocMethod\"/>\n    </class>\n    <extensions>\n      <pair source=\"cc\" header=\"h\"/>\n      <pair source=\"c\" header=\"h\"/>\n    </extensions>\n  </Objective-C-extensions>\n  <codeStyleSettings language=\"ObjectiveC\">\n    <option name=\"RIGHT_MARGIN\" value=\"80\"/>\n    <option name=\"KEEP_BLANK_LINES_BEFORE_RBRACE\" value=\"1\"/>\n    <option name=\"BLANK_LINES_BEFORE_IMPORTS\" value=\"0\"/>\n    <option name=\"BLANK_LINES_AFTER_IMPORTS\" value=\"0\"/>\n    <option name=\"BLANK_LINES_AROUND_CLASS\" value=\"0\"/>\n    <option name=\"BLANK_LINES_AROUND_METHOD\" value=\"0\"/>\n    <option name=\"BLANK_LINES_AROUND_METHOD_IN_INTERFACE\" value=\"0\"/>\n    <option name=\"ALIGN_MULTILINE_BINARY_OPERATION\" value=\"false\"/>\n    <option name=\"BINARY_OPERATION_SIGN_ON_NEXT_LINE\" value=\"true\"/>\n    <option name=\"FOR_STATEMENT_WRAP\" value=\"1\"/>\n    <option name=\"ASSIGNMENT_WRAP\" value=\"1\"/>\n    <indentOptions>\n      <option name=\"INDENT_SIZE\" value=\"2\"/>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\"/>\n    </indentOptions>\n  </codeStyleSettings>\n</code_scheme>"
  },
  {
    "path": "pebble/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>io.pebbletemplates</groupId>\n    <artifactId>pebble-project</artifactId>\n    <version>4.1.2-SNAPSHOT</version>\n  </parent>\n\n  <artifactId>pebble</artifactId>\n\n  <packaging>jar</packaging>\n  <name>Pebble</name>\n\n  <description>Templating engine for Java.</description>\n  <url>http://pebbletemplates.io</url>\n\n  <properties>\n    <unbescape.version>1.1.6.RELEASE</unbescape.version>\n    <slf4j.version>2.0.17</slf4j.version>\n    <caffeine.version>3.2.3</caffeine.version>\n    <jakarata.servlet-api.version>6.1.0</jakarata.servlet-api.version>\n    <servlet-api.version>2.5</servlet-api.version>\n\n    <assertj.version>3.27.7</assertj.version>\n    <logback-classic.version>1.5.32</logback-classic.version>\n    <junit-jupiter.version>6.0.3</junit-jupiter.version>\n  </properties>\n\n  <dependencies>\n    <!-- required dependencies -->\n    <dependency>\n      <groupId>org.unbescape</groupId>\n      <artifactId>unbescape</artifactId>\n      <version>${unbescape.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n\n    <!-- optional dependencies -->\n    <dependency>\n      <groupId>com.github.ben-manes.caffeine</groupId>\n      <artifactId>caffeine</artifactId>\n      <version>${caffeine.version}</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>servlet-api</artifactId>\n      <version>${servlet-api.version}</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <groupId>jakarta.servlet</groupId>\n      <version>${jakarata.servlet-api.version}</version>\n      <optional>true</optional>\n    </dependency>\n\n    <!-- testing dependencies -->\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n      <version>${logback-classic.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter</artifactId>\n      <version>${junit-jupiter.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <version>${assertj.version}</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>biz.aQute.bnd</groupId>\n        <artifactId>bnd-maven-plugin</artifactId>\n        <version>7.2.3</version>\n        <executions>\n          <execution>\n            <id>generate-osgi-manifest</id>\n            <goals>\n              <goal>bnd-process</goal>\n            </goals>\n            <configuration>\n              <bnd><![CDATA[\n                Bundle-SymbolicName: io.pebbletemplates.pebble\n                Automatic-Module-Name: io.pebbletemplates\n                Import-Package: \\\n                  com.github.benmanes.caffeine.*;version=\"[3.2,4)\";resolution:=optional, \\\n                  javax.servlet;version=\"[2.5,5)\";resolution:=optional, \\\n                  jakarta.servlet;version=\"[6.1,7)\";resolution:=optional, \\\n                  *\n                -exportcontents: *;-noimport:=true\n                -noextraheaders: true\n              ]]></bnd>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>\n          </archive>\n        </configuration>\n      </plugin>\n      <plugin>\n        <artifactId>maven-assembly-plugin</artifactId>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>single</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <descriptorRefs>\n            <descriptorRef>jar-with-dependencies</descriptorRef>\n          </descriptorRefs>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/PebbleEngine.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\n\nimport io.pebbletemplates.pebble.attributes.methodaccess.BlacklistMethodAccessValidator;\nimport io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;\nimport io.pebbletemplates.pebble.cache.CacheKey;\nimport io.pebbletemplates.pebble.cache.PebbleCache;\nimport io.pebbletemplates.pebble.cache.tag.ConcurrentMapTagCache;\nimport io.pebbletemplates.pebble.cache.tag.NoOpTagCache;\nimport io.pebbletemplates.pebble.cache.template.ConcurrentMapTemplateCache;\nimport io.pebbletemplates.pebble.cache.template.NoOpTemplateCache;\nimport io.pebbletemplates.pebble.error.LoaderException;\nimport io.pebbletemplates.pebble.extension.*;\nimport io.pebbletemplates.pebble.extension.escaper.EscapingStrategy;\nimport io.pebbletemplates.pebble.lexer.LexerImpl;\nimport io.pebbletemplates.pebble.lexer.Syntax;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.node.RootNode;\nimport io.pebbletemplates.pebble.parser.Parser;\nimport io.pebbletemplates.pebble.parser.ParserImpl;\nimport io.pebbletemplates.pebble.parser.ParserOptions;\nimport io.pebbletemplates.pebble.template.EvaluationOptions;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.TypeUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.util.Locale;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Function;\n\n/**\n * The main class used for compiling templates. The PebbleEngine is responsible for delegating\n * responsibility to the lexer, parser, compiler, and template cache.\n *\n * @author Mitchell\n */\npublic class PebbleEngine {\n\n  private final Logger logger = LoggerFactory.getLogger(PebbleEngine.class);\n\t\n  private final Loader<?> loader;\n\n  private final Syntax syntax;\n\n  private final boolean strictVariables;\n\n  private final Locale defaultLocale;\n\n  private final int maxRenderedSize;\n\n  private final PebbleCache<CacheKey, Object> tagCache;\n\n  private final ExecutorService executorService;\n\n  private final PebbleCache<Object, PebbleTemplate> templateCache;\n\n  private final ExtensionRegistry extensionRegistry;\n\n  private final ParserOptions parserOptions;\n\n  private final EvaluationOptions evaluationOptions;\n\n  /**\n   * Constructor for the Pebble Engine given an instantiated Loader. This method does only load\n   * those userProvidedExtensions listed here.\n   *\n   * @param loader The template loader for this engine\n   * @param syntax the syntax to use for parsing the templates.\n   */\n  private PebbleEngine(Loader<?> loader,\n      Syntax syntax,\n      boolean strictVariables,\n      Locale defaultLocale,\n      int maxRenderedSize,\n      PebbleCache<CacheKey, Object> tagCache,\n      PebbleCache<Object, PebbleTemplate> templateCache,\n      ExecutorService executorService,\n      ExtensionRegistry extensionRegistry,\n      ParserOptions parserOptions,\n      EvaluationOptions evaluationOptions) {\n\n    this.loader = loader;\n    this.syntax = syntax;\n    this.strictVariables = strictVariables;\n    this.defaultLocale = defaultLocale;\n    this.maxRenderedSize = maxRenderedSize;\n    this.tagCache = tagCache;\n    this.executorService = executorService;\n    this.templateCache = templateCache;\n    this.extensionRegistry = extensionRegistry;\n    this.parserOptions = parserOptions;\n    this.evaluationOptions = evaluationOptions;\n  }\n\n  /**\n   * Loads, parses, and compiles a template into an instance of PebbleTemplate and returns this\n   * instance.\n   *\n   * @param templateName The name of the template\n   * @return PebbleTemplate The compiled version of the template\n   */\n  public PebbleTemplate getTemplate(String templateName) {\n    return this.getTemplate(templateName, this.loader);\n  }\n\n  /**\n   * Loads, parses, and compiles a template using a StringLoader into an instance of PebbleTemplate\n   * and returns this instance.\n   *\n   * @param templateName The name of the template\n   * @return PebbleTemplate The compiled version of the template\n   */\n  public PebbleTemplate getLiteralTemplate(String templateName) {\n    return this.getTemplate(templateName, new StringLoader());\n  }\n\n  private PebbleTemplate getTemplate(String templateName, Loader loader) {\n    /*\n     * template name will be null if user uses the extends tag with an\n     * expression that evaluates to null\n     */\n    if (templateName == null) {\n      return null;\n    }\n\n    if (loader == null) {\n      throw new LoaderException(null, \"Loader has not yet been specified.\");\n    }\n\n    Object cacheKey = loader.createCacheKey(templateName);\n    return this.templateCache\n        .computeIfAbsent(cacheKey, k -> this.getPebbleTemplate(templateName, loader, cacheKey));\n  }\n\n  private PebbleTemplate getPebbleTemplate(String templateName, Loader loader, Object cacheKey) {\n\n    Reader templateReader = loader.getReader(cacheKey);\n    \n    try {\n      this.logger.trace(\"Tokenizing template named {}\", templateName);\n      LexerImpl lexer = new LexerImpl(this.syntax,\n          this.extensionRegistry.getUnaryOperators().values(),\n          this.extensionRegistry.getBinaryOperators().values());\n      TokenStream tokenStream = lexer.tokenize(templateReader, templateName);\n      this.logger.trace(\"TokenStream: {}\", tokenStream);\n      \n      Parser parser = new ParserImpl(this.extensionRegistry.getUnaryOperators(),\n          this.extensionRegistry.getBinaryOperators(), this.extensionRegistry.getTokenParsers(),\n              this.parserOptions);\n      RootNode root = parser.parse(tokenStream);\n\n      PebbleTemplateImpl instance = new PebbleTemplateImpl(this, root, templateName);\n\n      for (NodeVisitorFactory visitorFactory : this.extensionRegistry.getNodeVisitors()) {\n        visitorFactory.createVisitor(instance).visit(root);\n      }\n\n      return instance;\n\n    } finally {\n      try {\n        templateReader.close();\n      } catch (IOException e) {\n        // can't do much about it\n      }\n    }\n  }\n\n  /**\n   * Returns the loader\n   *\n   * @return The loader\n   */\n  public Loader<?> getLoader() {\n    return this.loader;\n  }\n\n  /**\n   * Returns the template cache\n   *\n   * @return The template cache\n   */\n  public PebbleCache<Object, PebbleTemplate> getTemplateCache() {\n    return this.templateCache;\n  }\n\n  /**\n   * Returns the strict variables setting\n   *\n   * @return The strict variables setting\n   */\n  public boolean isStrictVariables() {\n    return this.strictVariables;\n  }\n\n  /**\n   * Returns the default locale\n   *\n   * @return The default locale\n   */\n  public Locale getDefaultLocale() {\n    return this.defaultLocale;\n  }\n\n  /**\n   * Returns the max rendered size.\n   *\n   *  @return The max rendered size.\n   */\n  public int getMaxRenderedSize() {\n    return this.maxRenderedSize;\n  }\n\n  /**\n   * Returns the executor service\n   *\n   * @return The executor service\n   */\n  public ExecutorService getExecutorService() {\n    return this.executorService;\n  }\n\n  /**\n   * Returns the syntax which is used by this PebbleEngine.\n   *\n   * @return the syntax used by the PebbleEngine.\n   */\n  public Syntax getSyntax() {\n    return this.syntax;\n  }\n\n  /**\n   * Returns the extension registry.\n   *\n   * @return The extension registry\n   */\n  public ExtensionRegistry getExtensionRegistry() {\n    return this.extensionRegistry;\n  }\n\n  /**\n   * Returns the tag cache\n   *\n   * @return The tag cache\n   */\n  public PebbleCache<CacheKey, Object> getTagCache() {\n    return this.tagCache;\n  }\n\n  /**\n   * A builder to configure and construct an instance of a PebbleEngine.\n   */\n  public static class Builder {\n\n    private Loader<?> loader;\n\n    private Syntax syntax;\n\n    private boolean strictVariables = false;\n\n    private boolean enableNewLineTrimming = true;\n\n    private Locale defaultLocale;\n\n    private int maxRenderedSize = -1;\n\n    private ExecutorService executorService;\n\n    private PebbleCache<Object, PebbleTemplate> templateCache;\n\n    private boolean cacheActive = true;\n\n    private PebbleCache<CacheKey, Object> tagCache;\n\n    private boolean literalDecimalTreatedAsInteger = false;\n\n    private boolean greedyMatchMethod = false;\n\n    private boolean literalNumbersAsBigDecimals = false;\n\n    private MethodAccessValidator methodAccessValidator = new BlacklistMethodAccessValidator();\n\n    private final ExtensionRegistryFactory factory = new ExtensionRegistryFactory();\n\n    /**\n     * Creates the builder.\n     */\n    public Builder() {\n\n    }\n\n    /**\n     * Sets the loader used to find templates.\n     *\n     * @param loader A template loader\n     * @return This builder object\n     */\n    public Builder loader(Loader<?> loader) {\n      this.loader = loader;\n      return this;\n    }\n\n    /**\n     * Adds an extension, can be safely invoked several times to add different extensions.\n     *\n     * @param extensions One or more extensions to add\n     * @return This builder object\n     */\n    public Builder extension(Extension... extensions) {\n      this.factory.extension(extensions);\n      return this;\n    }\n\n    /**\n     * Sets the syntax to be used.\n     *\n     * @param syntax The syntax to be used\n     * @return This builder object\n     */\n    public Builder syntax(Syntax syntax) {\n      this.syntax = syntax;\n      return this;\n    }\n\n    /**\n     * Changes the <code>strictVariables</code> setting of the PebbleEngine. The default value of\n     * this setting is \"false\".\n     * <p>\n     * The following examples will all print empty strings if strictVariables is false but will\n     * throw exceptions if it is true:\n     * </p>\n     * {{ nonExistingVariable }} {{ nonExistingVariable.attribute }} {{ nullVariable.attribute }} {{\n     * existingVariable.nullAttribute.attribute }} {{ existingVariable.nonExistingAttribute }} {{\n     * array[-1] }}\n     *\n     * @param strictVariables Whether or not strict variables is used\n     * @return This builder object\n     */\n    public Builder strictVariables(boolean strictVariables) {\n      this.strictVariables = strictVariables;\n      return this;\n    }\n\n    /**\n     * Changes the <code>newLineTrimming</code> setting of the PebbleEngine. The default value of\n     * this setting is \"true\".\n     * <p>\n     * By default, Pebble will trim a newline that immediately follows a Pebble tag. For example,\n     * <code>{{key1}}\\n{{key2}}</code> will have the newline removed.\n     * </p>\n     * <p>\n     * If <code>newLineTrimming</code> is set to false, then the first newline following a Pebble\n     * tag won't be trimmed.  All newlines will be preserved\n     * </p>\n     *\n     * @param enableNewLineTrimming Whether or not the newline should be trimmed.\n     * @return This builder object\n     */\n    public Builder newLineTrimming(boolean enableNewLineTrimming) {\n      this.enableNewLineTrimming = enableNewLineTrimming;\n      return this;\n    }\n\n    /**\n     * Sets the Locale passed to all templates constructed by this PebbleEngine.\n     * <p>\n     * An individual template can always be given a new locale during evaluation.\n     *\n     * @param defaultLocale The default locale\n     * @return This builder object\n     */\n    public Builder defaultLocale(Locale defaultLocale) {\n      this.defaultLocale = defaultLocale;\n      return this;\n    }\n\n    /**\n     * Sets the maximum size of the rendered template to protect against macro bombs.\n     * See for example https://github.com/PebbleTemplates/pebble/issues/526.\n     * If the rendered template exceeds this limit, then a PebbleException is thrown.\n     * The default value is -1 and it means unlimited.\n     * @param maxRenderedSize The maximum allowed size of the rendered template.\n     * @return This builder object.\n     */\n    public Builder maxRenderedSize(int maxRenderedSize) {\n      this.maxRenderedSize = maxRenderedSize;\n      return this;\n    }\n\n    /**\n     * Sets the executor service which is required if using one of Pebble's multithreading features\n     * such as the \"parallel\" tag.\n     *\n     * @param executorService The executor service\n     * @return This builder object\n     */\n    public Builder executorService(ExecutorService executorService) {\n      this.executorService = executorService;\n      return this;\n    }\n\n    /**\n     * Sets the cache used by the engine to store compiled PebbleTemplate instances.\n     *\n     * @param templateCache The template cache\n     * @return This builder object\n     */\n    public Builder templateCache(PebbleCache<Object, PebbleTemplate> templateCache) {\n      this.templateCache = templateCache;\n      return this;\n    }\n\n    /**\n     * Sets the cache used by the \"cache\" tag.\n     *\n     * @param tagCache The tag cache\n     * @return This builder object\n     */\n    public Builder tagCache(PebbleCache<CacheKey, Object> tagCache) {\n      this.tagCache = tagCache;\n      return this;\n    }\n\n    /**\n     * Sets whether or not escaping should be performed automatically. The default value of this\n     * setting is \"true\".\n     *\n     * @param autoEscaping The auto escaping setting.\n     * @return This builder object\n     */\n    public Builder autoEscaping(boolean autoEscaping) {\n      this.factory.autoEscaping(autoEscaping);\n      return this;\n    }\n\n    /**\n     * Sets whether or not core operators overrides should be allowed. The default value of this\n     * setting is \"false\".\n     *\n     * @param allowOverrideCoreOperators Whether or not core operators overrides should be allowed.\n     * @return This builder object\n     */\n    public Builder allowOverrideCoreOperators(boolean allowOverrideCoreOperators) {\n      this.factory.allowOverrideCoreOperators(allowOverrideCoreOperators);\n      return this;\n    }\n\n    /**\n     * Sets the default escaping strategy of the built-in escaper extension.\n     *\n     * @param strategy The name of the default escaping strategy\n     * @return This builder object\n     */\n    public Builder defaultEscapingStrategy(String strategy) {\n      this.factory.defaultEscapingStrategy(strategy);\n      return this;\n    }\n\n    /**\n     * Adds an escaping strategy to the built-in escaper extension.\n     *\n     * @param name The name of the escaping strategy\n     * @param strategy The strategy implementation\n     * @return This builder object\n     */\n    public Builder addEscapingStrategy(String name, EscapingStrategy strategy) {\n      this.factory.addEscapingStrategy(name, strategy);\n      return this;\n    }\n\n    /**\n     * Enable/disable all caches, i.e. cache used by the engine to store compiled PebbleTemplate\n     * instances and tags cache. The default value of this setting is \"true\".\n     *\n     * @param cacheActive toggle to enable/disable all caches\n     * @return This builder object\n     */\n    public Builder cacheActive(boolean cacheActive) {\n      this.cacheActive = cacheActive;\n      return this;\n    }\n\n    /**\n     * Validator that can be used to validate object/method access\n     *\n     * @param methodAccessValidator Validator that can be used to validate object/method access\n     * @return This builder object\n     */\n    public Builder methodAccessValidator(MethodAccessValidator methodAccessValidator) {\n      this.methodAccessValidator = methodAccessValidator;\n      return this;\n    }\n\n    /**\n     * Enable/disable treat literal decimal as Integer. Default is disabled, treated as Long.\n     *\n     * @param literalDecimalTreatedAsInteger toggle to enable/disable literal decimal treated as\n     * integer\n     * @return This builder object\n     */\n    public Builder literalDecimalTreatedAsInteger(boolean literalDecimalTreatedAsInteger) {\n      this.literalDecimalTreatedAsInteger = literalDecimalTreatedAsInteger;\n      return this;\n    }\n\n    /**\n     * Enable/disable treat literal numbers as BigDecimals. Default is disabled, treated as Long/Double.\n     *\n     * @param literalNumbersAsBigDecimals toggle to enable/disable literal numbers treated as\n     * BigDecimals\n     * @return This builder object\n     */\n    public Builder literalNumbersAsBigDecimals(boolean literalNumbersAsBigDecimals) {\n      this.literalNumbersAsBigDecimals = literalNumbersAsBigDecimals;\n      return this;\n    }\n\n    /**\n     * Enable/disable greedy matching mode for finding java method. Default is disabled. If enabled,\n     * when can not find perfect method (method name, parameter length and parameter type are all\n     * satisfied), reduce the limit of the parameter type, try to find other method which has\n     * compatible parameter types.\n     *\n     * For example,\n     * <pre> {{ obj.number(2) }} </pre>\n     * the expression can match all of below methods.\n     * <pre>\n     *   public Long getNumber(Long v) {...}\n     *   public Integer getNumber(Integer v) {...}\n     *   public Short getNumber(Short v) {...}\n     *   public Byte getNumber(Byte v) {...}\n     *   ...\n     * </pre>\n     *\n     * @param greedyMatchMethod toggle to enable/disable greedy match method\n     * @return This builder object\n     * @see TypeUtils#compatibleCast(Object, Class)\n     */\n    public Builder greedyMatchMethod(boolean greedyMatchMethod) {\n      this.greedyMatchMethod = greedyMatchMethod;\n      return this;\n    }\n\n    /**\n     * Registers an implementation of {@link ExtensionCustomizer} to change runtime-behaviour of standard\n     * functionality.\n     *\n     * @param customizer The customizer which wraps any non-user-provided extension\n     * @return This build object\n     */\n    public Builder registerExtensionCustomizer(Function<Extension, ExtensionCustomizer> customizer) {\n      this.factory.registerExtensionCustomizer(customizer);\n      return this;\n    }\n\n    /**\n     * Creates the PebbleEngine instance.\n     *\n     * @return A PebbleEngine object that can be used to create PebbleTemplate objects.\n     */\n    public PebbleEngine build() {\n\n      ExtensionRegistry extensionRegistry = this.factory.buildExtensionRegistry();\n\n      // default loader\n      if (this.loader == null) {\n        this.loader = new ClasspathLoader();\n      }\n\n      // default locale\n      if (this.defaultLocale == null) {\n        this.defaultLocale = Locale.getDefault();\n      }\n\n      if (this.cacheActive) {\n        // default caches\n        if (this.templateCache == null) {\n          this.templateCache = new ConcurrentMapTemplateCache();\n        }\n\n        if (this.tagCache == null) {\n          this.tagCache = new ConcurrentMapTagCache();\n        }\n      } else {\n        this.templateCache = new NoOpTemplateCache();\n        this.tagCache = new NoOpTagCache();\n      }\n\n      if (this.syntax == null) {\n        this.syntax = new Syntax.Builder().setEnableNewLineTrimming(this.enableNewLineTrimming)\n            .build();\n      }\n\n      ParserOptions parserOptions = new ParserOptions();\n      parserOptions.setLiteralDecimalTreatedAsInteger(this.literalDecimalTreatedAsInteger);\n      parserOptions.setLiteralNumbersAsBigDecimals(this.literalNumbersAsBigDecimals);\n\n      EvaluationOptions evaluationOptions = new EvaluationOptions(this.greedyMatchMethod,\n          this.methodAccessValidator);\n      return new PebbleEngine(this.loader, this.syntax, this.strictVariables, this.defaultLocale, this.maxRenderedSize,\n          this.tagCache, this.templateCache,\n          this.executorService, extensionRegistry, parserOptions, evaluationOptions);\n    }\n  }\n\n  public EvaluationOptions getEvaluationOptions() {\n    return this.evaluationOptions;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/ArrayResolver.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\nimport io.pebbletemplates.pebble.error.AttributeNotFoundException;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\n\nimport java.lang.reflect.Array;\n\nclass ArrayResolver implements AttributeResolver {\n\n  static final ArrayResolver INSTANCE = new ArrayResolver();\n\n  private ArrayResolver() {\n  }\n\n  @Override\n  public ResolvedAttribute resolve(Object instance,\n      Object attributeNameValue,\n      Object[] argumentValues,\n      ArgumentsNode args,\n      EvaluationContextImpl context,\n      String filename,\n      int lineNumber) {\n    String attributeName = String.valueOf(attributeNameValue);\n    int index = this.getIndex(attributeName);\n    int length = Array.getLength(instance);\n    if (index < 0 || index >= length) {\n      if (context.isStrictVariables()) {\n        throw new AttributeNotFoundException(null,\n            \"Index out of bounds while accessing array with strict variables on.\",\n            attributeName, lineNumber, filename);\n      } else {\n        return new ResolvedAttribute(null);\n      }\n    }\n    return new ResolvedAttribute(Array.get(instance, index));\n  }\n\n  private int getIndex(String attributeName) {\n    try {\n      return Integer.parseInt(attributeName);\n    } catch (NumberFormatException e) {\n      return -1;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/AttributeResolver.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\n\n/**\n * Resolves instance attributes\n */\npublic interface AttributeResolver {\n\n  /**\n   * Attempts to resolve an attribute of the given instance. If this method returns {@code null},\n   * Pebble asks the next attribute resolver in the list.\n   *\n   * @param instance The object which is being accessed\n   * @param attributeNameValue The name of the attribute to resolve\n   * @param argumentValues fully evaluated positional arguments\n   * @param args The arguments\n   * @param context The evaluation context\n   * @param filename Filename of the template\n   * @param lineNumber the line number on which the expression is defined on.\n   * @return a {@link ResolvedAttribute} wrapping the attribute value, or {@code null} if the\n   * attribute could not be resolved\n   */\n  ResolvedAttribute resolve(Object instance,\n      Object attributeNameValue,\n      Object[] argumentValues,\n      ArgumentsNode args,\n      EvaluationContextImpl context,\n      String filename,\n      int lineNumber);\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/DefaultAttributeResolver.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.MacroAttributeProvider;\nimport io.pebbletemplates.pebble.utils.TypeUtils;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Member;\nimport java.lang.reflect.Method;\nimport java.util.List;\nimport java.util.Map;\n\npublic class DefaultAttributeResolver implements AttributeResolver {\n\n  private final MemberCacheUtils memberCacheUtils = new MemberCacheUtils();\n\n  @Override\n  public ResolvedAttribute resolve(Object instance,\n      Object attributeNameValue,\n      Object[] argumentValues,\n      ArgumentsNode args,\n      EvaluationContextImpl context,\n      String filename,\n      int lineNumber) {\n    if (instance != null) {\n      String attributeName = String.valueOf(attributeNameValue);\n\n      Class<?>[] argumentTypes = this.getArgumentTypes(argumentValues);\n      Member member = this.memberCacheUtils.getMember(instance, attributeName, argumentTypes);\n      if (member == null) {\n        if (argumentValues == null) {\n\n          // first we check maps\n          if (instance instanceof Map) {\n            return MapResolver.INSTANCE\n                .resolve(instance, attributeNameValue, null, args, context, filename, lineNumber);\n          }\n\n          // then we check arrays\n          if (instance.getClass().isArray()) {\n            return ArrayResolver.INSTANCE\n                .resolve(instance, attributeNameValue, null, args, context, filename, lineNumber);\n          }\n\n          // then lists\n          if (instance instanceof List) {\n            ResolvedAttribute resolvedAttribute = ListResolver.INSTANCE\n                .resolve(instance, attributeNameValue, null, args, context, filename, lineNumber);\n            if (resolvedAttribute != null) {\n              return resolvedAttribute;\n            }\n          }\n        }\n\n        if (instance instanceof MacroAttributeProvider) {\n          return MacroResolver.INSTANCE\n              .resolve(instance, attributeNameValue, argumentValues, args, context, filename,\n                  lineNumber);\n        }\n\n        member = this.memberCacheUtils\n            .cacheMember(instance, attributeName, argumentTypes, context, filename, lineNumber);\n      }\n\n      if (member != null) {\n        return new ResolvedAttribute(this.invokeMember(instance, member, argumentValues, filename, lineNumber));\n      }\n    }\n    return null;\n  }\n\n  private Class<?>[] getArgumentTypes(Object[] argumentValues) {\n    if (argumentValues != null) {\n      Class<?>[] argumentTypes = new Class<?>[argumentValues.length];\n\n      for (int i = 0; i < argumentValues.length; i++) {\n        Object o = argumentValues[i];\n        if (o == null) {\n          argumentTypes[i] = null;\n        } else {\n          argumentTypes[i] = o.getClass();\n        }\n      }\n      return argumentTypes;\n    }\n\n    return new Class<?>[0];\n  }\n\n  /**\n   * Invoke the \"Member\" that was found via reflection.\n   */\n  private Object invokeMember(Object object, Member member, Object[] argumentValues, String filename, int lineNumber) {\n    Object result = null;\n    try {\n      if (member instanceof Method) {\n        Method method = (Method) member;\n        argumentValues = TypeUtils.compatibleCast(argumentValues, method.getParameterTypes());\n        result = method.invoke(object, argumentValues);\n      } else if (member instanceof Field) {\n        result = ((Field) member).get(object);\n      }\n\n    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {\n      throw new PebbleException(e, \"Could not call \" + member.getName(), lineNumber, filename);\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/ListResolver.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\nimport io.pebbletemplates.pebble.error.AttributeNotFoundException;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\n\nimport java.util.List;\n\nclass ListResolver implements AttributeResolver {\n\n  static final ListResolver INSTANCE = new ListResolver();\n\n  private ListResolver() {\n  }\n\n  @Override\n  public ResolvedAttribute resolve(Object instance,\n      Object attributeNameValue,\n      Object[] argumentValues,\n      ArgumentsNode args,\n      EvaluationContextImpl context,\n      String filename,\n      int lineNumber) {\n    String attributeName = String.valueOf(attributeNameValue);\n\n    @SuppressWarnings(\"unchecked\") List<Object> list = (List<Object>) instance;\n\n    int index;\n    try {\n      index = Integer.parseInt(attributeName);\n    } catch (NumberFormatException e) {\n      return null;\n    }\n    int length = list.size();\n\n    if (index < 0 || index >= length) {\n      if (context.isStrictVariables()) {\n        throw new AttributeNotFoundException(null,\n            \"Index out of bounds while accessing array with strict variables on.\",\n            attributeName, lineNumber, filename);\n      } else {\n        return new ResolvedAttribute(null);\n      }\n    }\n\n    return new ResolvedAttribute(list.get(index));\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/MacroResolver.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.MacroAttributeProvider;\n\nclass MacroResolver implements AttributeResolver {\n\n  static final MacroResolver INSTANCE = new MacroResolver();\n\n  private MacroResolver() {\n  }\n\n  @Override\n  public ResolvedAttribute resolve(Object instance,\n      Object attributeNameValue,\n      Object[] argumentValues,\n      ArgumentsNode args,\n      EvaluationContextImpl context,\n      String filename,\n      int lineNumber) {\n    MacroAttributeProvider macroAttributeProvider = (MacroAttributeProvider) instance;\n    String attributeName = String.valueOf(attributeNameValue);\n    return new ResolvedAttribute(\n        macroAttributeProvider.macro(context, attributeName, args, false, lineNumber));\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/MapResolver.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\nimport io.pebbletemplates.pebble.error.AttributeNotFoundException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\n\nimport java.util.Map;\n\nclass MapResolver implements AttributeResolver {\n\n  static final MapResolver INSTANCE = new MapResolver();\n\n  private MapResolver() {\n  }\n\n  @Override\n  public ResolvedAttribute resolve(Object instance,\n      Object attributeNameValue,\n      Object[] argumentValues,\n      ArgumentsNode args,\n      EvaluationContextImpl context,\n      String filename,\n      int lineNumber) {\n    Map<?, ?> object = (Map<?, ?>) instance;\n    if (object.isEmpty() && !context.isStrictVariables()) {\n      return new ResolvedAttribute(null);\n    }\n\n    Object key;\n    if (attributeNameValue != null && Number.class\n        .isAssignableFrom(attributeNameValue.getClass())) {\n      Number keyAsNumber = (Number) attributeNameValue;\n\n      Class<?> keyClass = object.keySet().iterator().next().getClass();\n      key = this.cast(keyAsNumber, keyClass, filename, lineNumber);\n    } else {\n      key = attributeNameValue;\n    }\n\n    if(context.isStrictVariables() && !object.containsKey(key)) {\n      throw new AttributeNotFoundException(null, String.format(\n          \"Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.\",\n          attributeNameValue.toString(), object.getClass().getName()),\n          attributeNameValue.toString(), lineNumber, filename);\n    }\n\n    ResolvedAttribute resolvedAttribute = new ResolvedAttribute(object.get(key));\n    return resolvedAttribute;\n  }\n\n  private Object cast(Number number,\n      Class<?> desiredType,\n      String filename,\n      int lineNumber) {\n    if (desiredType == Long.class) {\n      return number.longValue();\n    } else if (desiredType == Integer.class) {\n      return number.intValue();\n    } else if (desiredType == Double.class) {\n      return number.doubleValue();\n    } else if (desiredType == Float.class) {\n      return number.floatValue();\n    } else if (desiredType == Short.class) {\n      return number.shortValue();\n    } else if (desiredType == Byte.class) {\n      return number.byteValue();\n    }\n    throw new PebbleException(null,\n        String.format(\"type %s not supported for key %s\", desiredType, number), lineNumber,\n        filename);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/MemberCacheUtils.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\nimport io.pebbletemplates.pebble.error.ClassAccessException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.EvaluationOptions;\n\nimport java.lang.reflect.AccessibleObject;\nimport java.lang.reflect.Member;\nimport java.lang.reflect.Method;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\nclass MemberCacheUtils {\n  private final ConcurrentHashMap<MemberCacheKey, Member> memberCache = new ConcurrentHashMap<>(100,\n          0.9f, 1);\n\n  Member getMember(Object instance, String attributeName, Class<?>[] argumentTypes) {\n    return this.memberCache.get(new MemberCacheKey(instance.getClass(), attributeName, argumentTypes));\n  }\n\n  Member cacheMember(Object instance,\n                     String attributeName,\n                     Class<?>[] argumentTypes,\n                     EvaluationContextImpl context,\n                     String filename,\n                     int lineNumber) {\n    Member member = this.reflect(instance, attributeName, argumentTypes, filename, lineNumber, context.getEvaluationOptions());\n    if (member != null) {\n      this.memberCache.put(new MemberCacheKey(instance.getClass(), attributeName, argumentTypes), member);\n    }\n    return member;\n  }\n\n  /**\n   * Performs the actual reflection to obtain a \"Member\" from a class.\n   */\n  private Member reflect(Object object, String attributeName, Class<?>[] parameterTypes,\n                         String filename, int lineNumber, EvaluationOptions evaluationOptions) {\n\n    Class<?> clazz = object.getClass();\n\n    // capitalize first letter of attribute for the following attempts\n    String attributeCapitalized = Character.toUpperCase(attributeName.charAt(0)) + attributeName.substring(1);\n\n    // search well known super classes first to avoid illegal reflective access\n    List<Class<?>> agenda = Arrays.asList(\n            List.class,\n            Set.class,\n            Map.class,\n            Map.Entry.class,\n            Collection.class,\n            Iterable.class,\n            clazz);\n\n    for (Class<?> type : agenda) {\n      if (!type.isAssignableFrom(clazz)) {\n        continue;\n      }\n\n      // check if attribute is a public method\n      Member result = this.findMethod(object, type, attributeName, parameterTypes, filename, lineNumber, evaluationOptions);\n\n      // public field\n      if (result == null) {\n        try {\n          result = type.getField(attributeName);\n        } catch (NoSuchFieldException | SecurityException e) {\n        }\n      }\n\n      // check get method\n      if (result == null) {\n        result = this.findMethod(object, type, \"get\" + attributeCapitalized, parameterTypes, filename, lineNumber, evaluationOptions);\n      }\n\n      // check is method\n      if (result == null) {\n        result = this.findMethod(object, type, \"is\" + attributeCapitalized, parameterTypes, filename, lineNumber, evaluationOptions);\n      }\n\n      // check has method\n      if (result == null) {\n        result = this.findMethod(object, type, \"has\" + attributeCapitalized, parameterTypes, filename, lineNumber, evaluationOptions);\n      }\n\n      if (result != null) {\n        ((AccessibleObject) result).setAccessible(true);\n        return result;\n      }\n    }\n    return null;\n  }\n\n  /**\n   * Finds an appropriate method by comparing if parameter types are compatible. This is more\n   * relaxed than class.getMethod.\n   */\n  private Method findMethod(Object object, Class<?> clazz, String name, Class<?>[] requiredTypes,\n                            String filename, int lineNumber, EvaluationOptions evaluationOptions) {\n    List<Method> candidates = this.getCandidates(clazz, name, requiredTypes);\n\n    // perfect match\n    Method bestMatch = null;\n    for (Method candidate : candidates) {\n      // check if method is even compatible\n      boolean compatibleTypes = true;\n      Class<?>[] types = candidate.getParameterTypes();\n      for (int i = 0; i < types.length; i++) {\n        if (requiredTypes[i] != null && !this.widen(types[i]).isAssignableFrom(requiredTypes[i])) {\n          compatibleTypes = false;\n          break;\n        }\n      }\n\n      // if it is compatible, check if it is a better match than the previous best\n      if (compatibleTypes) {\n        if (bestMatch == null) {\n          bestMatch = candidate;\n        } else {\n          Class<?>[] bestMatchParamTypes = bestMatch.getParameterTypes();\n          for (int i = 0; i < types.length; i++) {\n            // if the current method's param strictly extends the previous best, it is a better match\n            Class<?> widened = this.widen(bestMatchParamTypes[i]);\n            if (widened.isAssignableFrom(types[i]) && !widened.equals(types[i])) {\n              bestMatch = candidate;\n              break;\n            }\n          }\n        }\n      }\n    }\n    if (bestMatch != null) {\n      this.verifyUnsafeMethod(filename, lineNumber, evaluationOptions, object, bestMatch);\n      return bestMatch;\n    }\n\n    // greedy match\n    if (evaluationOptions.isGreedyMatchMethod()) {\n      for (Method candidate : candidates) {\n        boolean compatibleTypes = true;\n        Class<?>[] types = candidate.getParameterTypes();\n        for (int i = 0; i < types.length; i++) {\n          if (requiredTypes[i] != null && !this.isCompatibleType(types[i], requiredTypes[i])) {\n            compatibleTypes = false;\n            break;\n          }\n        }\n\n        if (compatibleTypes) {\n          this.verifyUnsafeMethod(filename, lineNumber, evaluationOptions, object, candidate);\n          return candidate;\n        }\n      }\n    }\n\n    return null;\n  }\n\n  private void verifyUnsafeMethod(String filename, int lineNumber,\n                                  EvaluationOptions evaluationOptions, Object object, Method method) {\n    boolean methodAccessAllowed = evaluationOptions.getMethodAccessValidator()\n            .isMethodAccessAllowed(object, method);\n    if (!methodAccessAllowed) {\n      throw new ClassAccessException(method, filename, lineNumber);\n    }\n  }\n\n\n  /**\n   * Performs a widening conversion (primitive to boxed type)\n   */\n  private Class<?> widen(Class<?> clazz) {\n    if (clazz == int.class) {\n      return Integer.class;\n    }\n    if (clazz == long.class) {\n      return Long.class;\n    }\n    if (clazz == double.class) {\n      return Double.class;\n    }\n    if (clazz == float.class) {\n      return Float.class;\n    }\n    if (clazz == short.class) {\n      return Short.class;\n    }\n    if (clazz == byte.class) {\n      return Byte.class;\n    }\n    if (clazz == boolean.class) {\n      return Boolean.class;\n    }\n    return clazz;\n  }\n\n  private List<Method> getCandidates(Class<?> clazz, String name, Object[] requiredTypes) {\n    List<Method> candidates = new ArrayList<>();\n    Method[] methods = clazz.getMethods();\n    for (Method m : methods) {\n      if (!m.getName().equalsIgnoreCase(name)) {\n        continue;\n      }\n\n      Class<?>[] types = m.getParameterTypes();\n      if (types.length != requiredTypes.length) {\n        continue;\n      }\n      candidates.add(m);\n    }\n    return candidates;\n  }\n\n  private boolean isCompatibleType(Class<?> type1, Class<?> type2) {\n    Class<?> widenType = this.widen(type1);\n    return Number.class.isAssignableFrom(widenType) && Number.class.isAssignableFrom(type2);\n  }\n\n  private class MemberCacheKey {\n\n    private final Class<?> clazz;\n    private final String attributeName;\n    private final Class<?>[] methodParameterTypes;\n\n    public MemberCacheKey(Class<?> clazz, String attributeName, Class<?>[] methodParameterTypes) {\n      this.clazz = clazz;\n      this.attributeName = attributeName;\n      this.methodParameterTypes = methodParameterTypes;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      if (this == o) {\n        return true;\n      }\n      if (o == null || this.getClass() != o.getClass()) {\n        return false;\n      }\n\n      MemberCacheKey that = (MemberCacheKey) o;\n\n      if (!this.clazz.equals(that.clazz)) {\n        return false;\n      }\n      if (!this.attributeName.equals(that.attributeName)) {\n        return false;\n      }\n\n      return Arrays.equals(this.methodParameterTypes, that.methodParameterTypes);\n    }\n\n    @Override\n    public int hashCode() {\n      int result = this.clazz.hashCode();\n      result = 31 * result + this.attributeName.hashCode();\n      result = 31 * result + Arrays.hashCode(this.methodParameterTypes);\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/ResolvedAttribute.java",
    "content": "package io.pebbletemplates.pebble.attributes;\n\npublic final class ResolvedAttribute {\n\n  public final Object evaluatedValue;\n\n  public ResolvedAttribute(Object evaluatedValue) {\n    this.evaluatedValue = evaluatedValue;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/methodaccess/BlacklistMethodAccessValidator.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\nimport java.lang.reflect.AccessibleObject;\nimport java.lang.reflect.Method;\n\npublic class BlacklistMethodAccessValidator implements MethodAccessValidator {\n\n  private static final String[] FORBIDDEN_METHODS = {\"getClass\",\n      \"wait\",\n      \"notify\",\n      \"notifyAll\"};\n\n  @Override\n  public boolean isMethodAccessAllowed(Object object, Method method) {\n    boolean methodForbidden = object instanceof Class\n        || object instanceof Runtime\n        || object instanceof Thread\n        || object instanceof ThreadGroup\n        || object instanceof System\n        || object instanceof AccessibleObject\n        || this.isUnsafeMethod(method);\n    return !methodForbidden;\n  }\n\n  private boolean isUnsafeMethod(Method member) {\n    return this.isAnyOfMethods(member, FORBIDDEN_METHODS);\n  }\n\n  private boolean isAnyOfMethods(Method member, String... methods) {\n    for (String method : methods) {\n      if (this.isMethodWithName(member, method)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private boolean isMethodWithName(Method member, String method) {\n    return member.getName().equals(method);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/methodaccess/MethodAccessValidator.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\nimport java.lang.reflect.Method;\n\npublic interface MethodAccessValidator {\n\n  boolean isMethodAccessAllowed(Object object, Method method);\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/attributes/methodaccess/NoOpMethodAccessValidator.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\nimport java.lang.reflect.Method;\n\npublic class NoOpMethodAccessValidator implements MethodAccessValidator {\n\n  @Override\n  public boolean isMethodAccessAllowed(Object object, Method method) {\n    return true;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/CacheKey.java",
    "content": "package io.pebbletemplates.pebble.cache;\n\nimport io.pebbletemplates.pebble.node.CacheNode;\n\nimport java.util.Locale;\n\n/**\n * Key to be used in the cache\n *\n * @author Eric Bussieres\n */\npublic class CacheKey {\n\n  private final CacheNode node;\n  private final String name;\n  private final Locale locale;\n\n  public CacheKey(CacheNode node, String name, Locale locale) {\n    this.node = node;\n    this.name = name;\n    this.locale = locale;\n  }\n\n  /**\n   * {@inheritDoc}\n   *\n   * @see Object#equals(Object)\n   */\n  @Override\n  public boolean equals(Object obj) {\n    if (this == obj) {\n      return true;\n    }\n    if (this.getClass() != obj.getClass()) {\n      return false;\n    }\n    CacheKey other = (CacheKey) obj;\n    if (!this.node.equals(other.node)) {\n      return false;\n    }\n    if (this.locale == null) {\n      if (other.locale != null) {\n        return false;\n      }\n    } else if (!this.locale.equals(other.locale)) {\n      return false;\n    }\n    if (this.name == null) {\n      return other.name == null;\n    } else {\n      return this.name.equals(other.name);\n    }\n  }\n\n  /**\n   * {@inheritDoc}\n   *\n   * @see Object#hashCode()\n   */\n  @Override\n  public int hashCode() {\n    final int prime = 31;\n    int result = this.node.hashCode();\n    result = prime * result + ((this.locale == null) ? 0 : this.locale.hashCode());\n    result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());\n    return result;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/PebbleCache.java",
    "content": "package io.pebbletemplates.pebble.cache;\n\nimport java.util.function.Function;\n\npublic interface PebbleCache<K, V> {\n\n  V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);\n\n  void invalidateAll();\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/tag/CaffeineTagCache.java",
    "content": "package io.pebbletemplates.pebble.cache.tag;\n\nimport com.github.benmanes.caffeine.cache.Cache;\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport io.pebbletemplates.pebble.cache.CacheKey;\nimport io.pebbletemplates.pebble.cache.PebbleCache;\n\nimport java.util.function.Function;\n\npublic class CaffeineTagCache implements PebbleCache<CacheKey, Object> {\n\n  private final Cache<CacheKey, Object> tagCache;\n\n  public CaffeineTagCache() {\n    this.tagCache = Caffeine.newBuilder()\n        .maximumSize(200)\n        .build();\n  }\n\n  public CaffeineTagCache(Cache<CacheKey, Object> tagCache) {\n    this.tagCache = tagCache;\n  }\n\n  @Override\n  public Object computeIfAbsent(CacheKey key, Function<? super CacheKey, ?> mappingFunction) {\n    return this.tagCache.get(key, mappingFunction);\n  }\n\n  @Override\n  public void invalidateAll() {\n    this.tagCache.invalidateAll();\n  }\n}\n\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/tag/ConcurrentMapTagCache.java",
    "content": "package io.pebbletemplates.pebble.cache.tag;\n\nimport io.pebbletemplates.pebble.cache.CacheKey;\nimport io.pebbletemplates.pebble.cache.PebbleCache;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.function.Function;\n\npublic class ConcurrentMapTagCache implements PebbleCache<CacheKey, Object> {\n\n  private final ConcurrentMap<CacheKey, Object> tagCache;\n\n  public ConcurrentMapTagCache() {\n    this.tagCache = new ConcurrentHashMap<>(200);\n  }\n\n  public ConcurrentMapTagCache(ConcurrentMap<CacheKey, Object> tagCache) {\n    this.tagCache = tagCache;\n  }\n\n  @Override\n  public Object computeIfAbsent(CacheKey key,\n      Function<? super CacheKey, ?> mappingFunction) {\n    return this.tagCache.computeIfAbsent(key, mappingFunction);\n  }\n\n  @Override\n  public void invalidateAll() {\n    this.tagCache.clear();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/tag/NoOpTagCache.java",
    "content": "package io.pebbletemplates.pebble.cache.tag;\n\nimport io.pebbletemplates.pebble.cache.CacheKey;\nimport io.pebbletemplates.pebble.cache.PebbleCache;\n\nimport java.util.function.Function;\n\npublic class NoOpTagCache implements PebbleCache<CacheKey, Object> {\n\n  @Override\n  public Object computeIfAbsent(CacheKey key,\n      Function<? super CacheKey, ?> mappingFunction) {\n    return mappingFunction.apply(key);\n  }\n\n  @Override\n  public void invalidateAll() {}\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/template/CaffeineTemplateCache.java",
    "content": "package io.pebbletemplates.pebble.cache.template;\n\nimport com.github.benmanes.caffeine.cache.Cache;\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport io.pebbletemplates.pebble.cache.PebbleCache;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.function.Function;\n\npublic class CaffeineTemplateCache implements PebbleCache<Object, PebbleTemplate> {\n\n  private final Cache<Object, PebbleTemplate> templateCache;\n\n  public CaffeineTemplateCache() {\n    this.templateCache = Caffeine.newBuilder()\n        .maximumSize(200)\n        .build();\n  }\n\n  public CaffeineTemplateCache(Cache<Object, PebbleTemplate> templateCache) {\n    this.templateCache = templateCache;\n  }\n\n  @Override\n  public PebbleTemplate computeIfAbsent(Object key,\n      Function<? super Object, ? extends PebbleTemplate> mappingFunction) {\n    return this.templateCache.get(key, mappingFunction);\n  }\n\n  @Override\n  public void invalidateAll() {\n    this.templateCache.invalidateAll();\n  }\n}\n\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/template/ConcurrentMapTemplateCache.java",
    "content": "package io.pebbletemplates.pebble.cache.template;\n\nimport io.pebbletemplates.pebble.cache.PebbleCache;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.function.Function;\n\npublic class ConcurrentMapTemplateCache implements PebbleCache<Object, PebbleTemplate> {\n\n  private final ConcurrentMap<Object, PebbleTemplate> templateCache;\n\n  public ConcurrentMapTemplateCache() {\n    this.templateCache = new ConcurrentHashMap<>(200);\n  }\n\n  public ConcurrentMapTemplateCache(ConcurrentMap<Object, PebbleTemplate> templateCache) {\n    this.templateCache = templateCache;\n  }\n\n  @Override\n  public PebbleTemplate computeIfAbsent(Object key,\n      Function<? super Object, ? extends PebbleTemplate> mappingFunction) {\n    return this.templateCache.computeIfAbsent(key, mappingFunction);\n  }\n\n  @Override\n  public void invalidateAll() {\n    this.templateCache.clear();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/cache/template/NoOpTemplateCache.java",
    "content": "package io.pebbletemplates.pebble.cache.template;\n\nimport io.pebbletemplates.pebble.cache.PebbleCache;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.function.Function;\n\npublic class NoOpTemplateCache implements PebbleCache<Object, PebbleTemplate> {\n\n  @Override\n  public PebbleTemplate computeIfAbsent(Object key,\n      Function<? super Object, ? extends PebbleTemplate> mappingFunction) {\n    return mappingFunction.apply(key);\n  }\n\n  @Override\n  public void invalidateAll() {}\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/error/AttributeNotFoundException.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.error;\n\npublic class AttributeNotFoundException extends PebbleException {\n\n  private static final long serialVersionUID = 3863732457312917327L;\n\n  private final String attributeName;\n\n  public AttributeNotFoundException(Throwable cause, String message, String attributeName,\n      int lineNumber,\n      String filename) {\n    super(cause, message, lineNumber, filename);\n    this.attributeName = attributeName;\n  }\n\n  public String getAttributeName() {\n    return this.attributeName;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/error/ClassAccessException.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.error;\n\nimport java.lang.reflect.Method;\n\npublic class ClassAccessException extends PebbleException {\n\n  private static final long serialVersionUID = 5109892021088141417L;\n\n  public ClassAccessException(Method method, String filename, Integer lineNumber) {\n    super(null, String.format(\"For security reasons access to %s method is denied.\", method),\n        lineNumber,\n        filename);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/error/LoaderException.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.error;\n\npublic class LoaderException extends PebbleException {\n\n  private static final long serialVersionUID = -6445262510797040243L;\n\n  public LoaderException(Throwable cause, String message) {\n    super(cause, message);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/error/ParserException.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.error;\n\npublic class ParserException extends PebbleException {\n\n  private static final long serialVersionUID = -3712498518512126529L;\n\n  public ParserException(Throwable cause, String message, int lineNumber, String filename) {\n    super(cause, message, lineNumber, filename);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/error/PebbleException.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.error;\n\npublic class PebbleException extends RuntimeException {\n\n  private static final long serialVersionUID = -2855774187093732189L;\n\n  protected final Integer lineNumber;\n\n  protected final String filename;\n\n  protected final String message;\n\n  public PebbleException(Throwable cause, String message) {\n    this(cause, message, null, null);\n  }\n\n  public PebbleException(Throwable cause, String message, Integer lineNumber, String filename) {\n    super(String.format(\"%s (%s:%s)\", message, filename == null ? \"?\" : filename,\n        lineNumber == null ? \"?\" : String.valueOf(lineNumber)), cause);\n    this.message = message;\n    this.lineNumber = lineNumber;\n    this.filename = filename;\n  }\n\n  /**\n   * Returns the line number on which the exception was thrown.\n   *\n   * @return the line number on which the exception was thrown.\n   */\n  public Integer getLineNumber() {\n    return this.lineNumber;\n  }\n\n  /**\n   * Returns the filename in which the exception was thrown.\n   *\n   * @return the filename in which the exception was thrown.\n   */\n  public String getFileName() {\n    return this.filename;\n  }\n\n  /**\n   * Returns the message which is set for the exception by Pebble. Its the message which is not\n   * enhanced with the line number and filename.\n   *\n   * @return the message which is set for the exception by Pebble.\n   */\n  public String getPebbleMessage() {\n    return this.message;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/error/RootAttributeNotFoundException.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.error;\n\npublic class RootAttributeNotFoundException extends AttributeNotFoundException {\n\n  private static final long serialVersionUID = 3863732457312917327L;\n\n  public RootAttributeNotFoundException(Throwable cause, String message, String attributeName,\n      int lineNumber,\n      String filename) {\n    super(cause, message, attributeName, lineNumber, filename);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/AbstractExtension.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.attributes.AttributeResolver;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\nimport java.util.List;\nimport java.util.Map;\n\npublic abstract class AbstractExtension implements Extension {\n\n  @Override\n  public List<TokenParser> getTokenParsers() {\n    return null;\n  }\n\n  @Override\n  public List<BinaryOperator> getBinaryOperators() {\n    return null;\n  }\n\n  @Override\n  public List<UnaryOperator> getUnaryOperators() {\n    return null;\n  }\n\n  @Override\n  public Map<String, Filter> getFilters() {\n    return null;\n  }\n\n  @Override\n  public Map<String, Test> getTests() {\n    return null;\n  }\n\n  @Override\n  public Map<String, Function> getFunctions() {\n    return null;\n  }\n\n  @Override\n  public Map<String, Object> getGlobalVariables() {\n    return null;\n  }\n\n  @Override\n  public List<NodeVisitorFactory> getNodeVisitors() {\n    return null;\n  }\n\n  @Override\n  public List<AttributeResolver> getAttributeResolver() {\n    return null;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/AbstractNodeVisitor.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.AutoEscapeNode;\nimport io.pebbletemplates.pebble.node.BlockNode;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.ExtendsNode;\nimport io.pebbletemplates.pebble.node.FlushNode;\nimport io.pebbletemplates.pebble.node.ForNode;\nimport io.pebbletemplates.pebble.node.IfNode;\nimport io.pebbletemplates.pebble.node.ImportNode;\nimport io.pebbletemplates.pebble.node.IncludeNode;\nimport io.pebbletemplates.pebble.node.MacroNode;\nimport io.pebbletemplates.pebble.node.NamedArgumentNode;\nimport io.pebbletemplates.pebble.node.Node;\nimport io.pebbletemplates.pebble.node.ParallelNode;\nimport io.pebbletemplates.pebble.node.PositionalArgumentNode;\nimport io.pebbletemplates.pebble.node.PrintNode;\nimport io.pebbletemplates.pebble.node.RootNode;\nimport io.pebbletemplates.pebble.node.SetNode;\nimport io.pebbletemplates.pebble.node.TextNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.Pair;\n\n/**\n * A base node visitor that can be extended for the sake of using it's navigational abilities.\n *\n * @author Mitchell\n */\npublic class AbstractNodeVisitor implements NodeVisitor {\n\n  private final PebbleTemplateImpl template;\n\n  public AbstractNodeVisitor(PebbleTemplateImpl template) {\n    this.template = template;\n  }\n\n  /**\n   * Default method used for unknown nodes such as nodes from a user provided extension.\n   */\n  @Override\n  public void visit(Node node) {\n  }\n\n  /*\n   * OVERLOADED NODES (keep alphabetized)\n   */\n  @Override\n  public void visit(ArgumentsNode node) {\n    if (node.getNamedArgs() != null) {\n      for (Node arg : node.getNamedArgs()) {\n        arg.accept(this);\n      }\n    }\n    if (node.getPositionalArgs() != null) {\n      for (Node arg : node.getPositionalArgs()) {\n        arg.accept(this);\n      }\n    }\n  }\n\n  @Override\n  public void visit(AutoEscapeNode node) {\n    node.getBody().accept(this);\n  }\n\n  @Override\n  public void visit(BlockNode node) {\n    node.getBody().accept(this);\n  }\n\n  @Override\n  public void visit(BodyNode node) {\n    for (Node child : node.getChildren()) {\n      child.accept(this);\n    }\n  }\n\n  @Override\n  public void visit(ExtendsNode node) {\n    node.getParentExpression().accept(this);\n  }\n\n  @Override\n  public void visit(FlushNode node) {\n\n  }\n\n  @Override\n  public void visit(ForNode node) {\n    node.getIterable().accept(this);\n    node.getBody().accept(this);\n    if (node.getElseBody() != null) {\n      node.getElseBody().accept(this);\n    }\n  }\n\n  @Override\n  public void visit(IfNode node) {\n    for (Pair<Expression<?>, BodyNode> pairs : node.getConditionsWithBodies()) {\n      pairs.getLeft().accept(this);\n      pairs.getRight().accept(this);\n    }\n    if (node.getElseBody() != null) {\n      node.getElseBody().accept(this);\n    }\n  }\n\n  @Override\n  public void visit(ImportNode node) {\n    node.getImportExpression().accept(this);\n  }\n\n  @Override\n  public void visit(IncludeNode node) {\n    node.getIncludeExpression().accept(this);\n  }\n\n  @Override\n  public void visit(MacroNode node) {\n    node.getBody().accept(this);\n    node.getArgs().accept(this);\n  }\n\n  @Override\n  public void visit(NamedArgumentNode node) {\n    if (node.getValueExpression() != null) {\n      node.getValueExpression().accept(this);\n    }\n  }\n\n  @Override\n  public void visit(ParallelNode node) {\n    node.getBody().accept(this);\n  }\n\n  @Override\n  public void visit(PositionalArgumentNode node) {\n    node.getValueExpression().accept(this);\n  }\n\n  @Override\n  public void visit(PrintNode node) {\n    node.getExpression().accept(this);\n  }\n\n  @Override\n  public void visit(RootNode node) {\n    node.getBody().accept(this);\n  }\n\n  @Override\n  public void visit(SetNode node) {\n    node.getValue().accept(this);\n  }\n\n  @Override\n  public void visit(TextNode node) {\n\n  }\n\n  protected PebbleTemplateImpl getTemplate() {\n    return this.template;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/Extension.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.attributes.AttributeResolver;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\nimport java.util.List;\nimport java.util.Map;\n\npublic interface Extension {\n\n  /**\n   * Use this method to provide custom filters.\n   *\n   * @return A list of filters. It is okay to return null.\n   */\n  Map<String, Filter> getFilters();\n\n  /**\n   * Use this method to provide custom tests.\n   *\n   * @return A list of tests. It is okay to return null.\n   */\n  Map<String, Test> getTests();\n\n  /**\n   * Use this method to provide custom functions.\n   *\n   * @return A list of functions. It is okay to return null.\n   */\n  Map<String, Function> getFunctions();\n\n  /**\n   * Use this method to provide custom tags.\n   *\n   * A TokenParser is used to parse a stream of tokens into Nodes which are then responsible for\n   * compiling themselves into Java.\n   *\n   * @return A list of TokenParsers. It is okay to return null.\n   */\n  List<TokenParser> getTokenParsers();\n\n  /**\n   * Use this method to provide custom binary operators.\n   *\n   * @return A list of Operators. It is okay to return null;\n   */\n  List<BinaryOperator> getBinaryOperators();\n\n  /**\n   * Use this method to provide custom unary operators.\n   *\n   * @return A list of Operators. It is okay to return null;\n   */\n  List<UnaryOperator> getUnaryOperators();\n\n  /**\n   * Use this method to provide variables available to all templates\n   *\n   * @return Map of global variables available to all templates\n   */\n  Map<String, Object> getGlobalVariables();\n\n  /**\n   * Node visitors will travel the AST tree during the compilation phase.\n   *\n   * @return a list of node visitors\n   */\n  List<NodeVisitorFactory> getNodeVisitors();\n\n  /**\n   * AttributeResolver will resolve instance attributes\n   *\n   * @return a list of attribute resolver\n   */\n  List<AttributeResolver> getAttributeResolver();\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/ExtensionCustomizer.java",
    "content": "package io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.attributes.AttributeResolver;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n *  Base class that allows implementing a customizer to modify Pebbles build-in extensions.\n *  It is meant to provide a way to remove or replace functions, filters, tags, etc. to change\n *  the standard behaviour. Use-cases can be down-stripping available functionality for security\n *  reasons.\n *\n *  Implementations of this class are meant to overwrite methods and access registered functionality\n *  before it is loaded into the PebbleEngine by calling super.\n *\n *  The ExentsionCustomizer can be registred via {@link PebbleEngine.Builder#registerExtensionCustomizer}\n *  and is applied for every non-user-provided extension.\n *\n */\npublic abstract class ExtensionCustomizer implements Extension {\n\n  private final Extension delegate;\n\n  public ExtensionCustomizer(Extension delegate) {\n    this.delegate = delegate;\n  }\n\n  @Override\n  public Map<String, Filter> getFilters() {\n    return delegate.getFilters();\n  }\n\n  @Override\n  public Map<String, Test> getTests() {\n    return delegate.getTests();\n  }\n\n  @Override\n  public Map<String, Function> getFunctions() {\n    return delegate.getFunctions();\n  }\n\n  @Override\n  public List<TokenParser> getTokenParsers() {\n    return delegate.getTokenParsers();\n  }\n\n  @Override\n  public List<BinaryOperator> getBinaryOperators() {\n    return delegate.getBinaryOperators();\n  }\n\n  @Override\n  public List<UnaryOperator> getUnaryOperators() {\n    return delegate.getUnaryOperators();\n  }\n\n  @Override\n  public Map<String, Object> getGlobalVariables() {\n    return delegate.getGlobalVariables();\n  }\n\n  @Override\n  public List<NodeVisitorFactory> getNodeVisitors() {\n    return delegate.getNodeVisitors();\n  }\n\n  @Override\n  public List<AttributeResolver> getAttributeResolver() {\n    return delegate.getAttributeResolver();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/ExtensionRegistry.java",
    "content": "package io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.attributes.AttributeResolver;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Storage for the extensions and the components retrieved from the various extensions.\n * <p>\n * Created by mitch_000 on 2015-11-28.\n */\npublic class ExtensionRegistry {\n\n  /**\n   * Unary operators used during the lexing phase.\n   */\n  private final Map<String, UnaryOperator> unaryOperators = new HashMap<>();\n\n  /**\n   * Binary operators used during the lexing phase.\n   */\n  private final Map<String, BinaryOperator> binaryOperators = new HashMap<>();\n\n  /**\n   * Token parsers used during the parsing phase.\n   */\n  private final Map<String, TokenParser> tokenParsers = new HashMap<>();\n\n  /**\n   * Node visitors available during the parsing phase.\n   */\n  private final List<NodeVisitorFactory> nodeVisitors = new ArrayList<>();\n\n  /**\n   * Filters used during the evaluation phase.\n   */\n  private final Map<String, Filter> filters = new HashMap<>();\n\n  /**\n   * Tests used during the evaluation phase.\n   */\n  private final Map<String, Test> tests = new HashMap<>();\n\n  /**\n   * Functions used during the evaluation phase.\n   */\n  private final Map<String, Function> functions = new HashMap<>();\n\n  /**\n   * Global variables available during the evaluation phase.\n   */\n  private final Map<String, Object> globalVariables = new HashMap<>();\n\n  private final List<AttributeResolver> attributeResolver = new ArrayList<>();\n\n  public ExtensionRegistry() {\n  }\n\n  public ExtensionRegistry(Collection<? extends Extension> extensions) {\n\n    for (Extension extension : extensions) {\n      addExtension(extension);\n    }\n  }\n\n  public void addOperatorOverridingExtension(Extension extension) {\n    addExtension(extension, true);\n  }\n\n  public void addExtension(Extension extension) {\n    addExtension(extension, false);\n  }\n\n  private void addExtension(Extension extension, boolean operatorOverriding) {\n    // token parsers\n    List<TokenParser> tokenParsers = extension.getTokenParsers();\n    if (tokenParsers != null) {\n      for (TokenParser tokenParser : tokenParsers) {\n        this.tokenParsers.put(tokenParser.getTag(), tokenParser);\n      }\n    }\n\n    // binary operators\n    List<BinaryOperator> binaryOperators = extension.getBinaryOperators();\n    if (binaryOperators != null) {\n      for (BinaryOperator operator : binaryOperators) {\n        if (operatorOverriding) {\n          this.binaryOperators.put(operator.getSymbol(), operator);\n        } else {\n          this.binaryOperators.putIfAbsent(operator.getSymbol(), operator);\n        }\n      }\n    }\n\n    // unary operators\n    List<UnaryOperator> unaryOperators = extension.getUnaryOperators();\n    if (unaryOperators != null) {\n      for (UnaryOperator operator : unaryOperators) {\n        if (operatorOverriding) {\n          this.unaryOperators.put(operator.getSymbol(), operator);\n        } else {\n          this.unaryOperators.putIfAbsent(operator.getSymbol(), operator);\n        }\n      }\n    }\n\n    // filters\n    Map<String, Filter> filters = extension.getFilters();\n    if (filters != null) {\n      this.filters.putAll(filters);\n    }\n\n    // tests\n    Map<String, Test> tests = extension.getTests();\n    if (tests != null) {\n      this.tests.putAll(tests);\n    }\n\n    // functions\n    Map<String, Function> functions = extension.getFunctions();\n    if (functions != null) {\n      this.functions.putAll(functions);\n    }\n\n    // global variables\n    Map<String, Object> globalVariables = extension.getGlobalVariables();\n    if (globalVariables != null) {\n      this.globalVariables.putAll(globalVariables);\n    }\n\n    // node visitors\n    List<NodeVisitorFactory> nodeVisitors = extension.getNodeVisitors();\n    if (nodeVisitors != null) {\n      this.nodeVisitors.addAll(nodeVisitors);\n    }\n\n    // attribute resolver\n    List<AttributeResolver> attributeResolvers = extension.getAttributeResolver();\n    if (attributeResolvers != null) {\n      this.attributeResolver.addAll(attributeResolvers);\n    }\n  }\n\n  public Filter getFilter(String name) {\n    return this.filters.get(name);\n  }\n\n  public Test getTest(String name) {\n    return this.tests.get(name);\n  }\n\n  public Function getFunction(String name) {\n    return this.functions.get(name);\n  }\n\n  public Map<String, BinaryOperator> getBinaryOperators() {\n    return this.binaryOperators;\n  }\n\n  public Map<String, UnaryOperator> getUnaryOperators() {\n    return this.unaryOperators;\n  }\n\n  public List<NodeVisitorFactory> getNodeVisitors() {\n    return this.nodeVisitors;\n  }\n\n  public Map<String, Object> getGlobalVariables() {\n    return this.globalVariables;\n  }\n\n  public Map<String, TokenParser> getTokenParsers() {\n    return this.tokenParsers;\n  }\n\n  public List<AttributeResolver> getAttributeResolver() {\n    return this.attributeResolver;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/ExtensionRegistryFactory.java",
    "content": "package io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.extension.core.AttributeResolverExtension;\nimport io.pebbletemplates.pebble.extension.i18n.I18nExtension;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.extension.core.CoreExtension;\nimport io.pebbletemplates.pebble.extension.escaper.EscaperExtension;\nimport io.pebbletemplates.pebble.extension.escaper.EscapingStrategy;\n\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\n/**\n * Provides configuration methods and builds the {@link ExtensionRegistry}. Used only internally by\n * the {@link PebbleEngine.Builder}.\n *\n */\npublic class ExtensionRegistryFactory {\n\n  private final List<Extension> userProvidedExtensions = new ArrayList<>();\n\n  private final EscaperExtension escaperExtension = new EscaperExtension();\n\n  private boolean allowOverrideCoreOperators = false;\n\n  private Function<Extension, Extension> customizer = Function.identity();\n\n  public ExtensionRegistry buildExtensionRegistry() {\n    ExtensionRegistry extensionRegistry = new ExtensionRegistry();\n\n    Stream.of(new CoreExtension(), this.escaperExtension, new I18nExtension())\n            .map(customizer::apply)\n            .forEach(extensionRegistry::addExtension);\n\n    for (Extension userProvidedExtension : this.userProvidedExtensions) {\n      if (this.allowOverrideCoreOperators) {\n        extensionRegistry.addOperatorOverridingExtension(userProvidedExtension);\n      } else {\n        extensionRegistry.addExtension(userProvidedExtension);\n      }\n    }\n\n    extensionRegistry.addExtension(customizer.apply(new AttributeResolverExtension()));\n\n    return extensionRegistry;\n  }\n\n  public void autoEscaping(boolean autoEscaping) {\n    this.escaperExtension.setAutoEscaping(autoEscaping);\n  }\n\n  public void addEscapingStrategy(String name, EscapingStrategy strategy) {\n    this.escaperExtension.addEscapingStrategy(name, strategy);\n  }\n\n  public void extension(Extension... extensions) {\n    Collections.addAll(this.userProvidedExtensions, extensions);\n  }\n\n  public void allowOverrideCoreOperators(boolean allowOverrideCoreOperators) {\n    this.allowOverrideCoreOperators = allowOverrideCoreOperators;\n  }\n\n  public void defaultEscapingStrategy(String strategy) {\n    this.escaperExtension.setDefaultStrategy(strategy);\n  }\n\n  public void registerExtensionCustomizer(Function<Extension, ExtensionCustomizer> customizer) {\n    this.customizer = customizer::apply;\n  }\n\n}"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/Filter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Map;\n\npublic interface Filter extends NamedArguments {\n\n  Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n               EvaluationContext context, int\n      lineNumber) throws PebbleException;\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/Function.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Map;\n\npublic interface Function extends NamedArguments {\n\n  Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                 int lineNumber);\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/NamedArguments.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport java.util.List;\n\npublic interface NamedArguments {\n\n  List<String> getArgumentNames();\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/NodeVisitor.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.AutoEscapeNode;\nimport io.pebbletemplates.pebble.node.BlockNode;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.ExtendsNode;\nimport io.pebbletemplates.pebble.node.FlushNode;\nimport io.pebbletemplates.pebble.node.ForNode;\nimport io.pebbletemplates.pebble.node.IfNode;\nimport io.pebbletemplates.pebble.node.ImportNode;\nimport io.pebbletemplates.pebble.node.IncludeNode;\nimport io.pebbletemplates.pebble.node.MacroNode;\nimport io.pebbletemplates.pebble.node.NamedArgumentNode;\nimport io.pebbletemplates.pebble.node.Node;\nimport io.pebbletemplates.pebble.node.ParallelNode;\nimport io.pebbletemplates.pebble.node.PositionalArgumentNode;\nimport io.pebbletemplates.pebble.node.PrintNode;\nimport io.pebbletemplates.pebble.node.RootNode;\nimport io.pebbletemplates.pebble.node.SetNode;\nimport io.pebbletemplates.pebble.node.TextNode;\n\n/**\n * Will visit all the nodes of the AST provided by the parser. The NodeVisitor is responsible for\n * the navigating the tree, it can extend AbstractNodeVisitor for help with this.\n *\n * A NodeVisitor can still use method overloading to visit expressions (it's just not required).\n *\n * <p>\n * The implementor does not need to make sure that the implementation is thread-safe.\n *\n * @author Mitchell\n */\npublic interface NodeVisitor {\n\n  /**\n   * Default method invoked with unknown nodes such as nodes provided by user extensions.\n   *\n   * @param node Node to visit\n   */\n  void visit(Node node);\n\n  /*\n   * OVERLOADED NODES (keep alphabetized)\n   */\n  void visit(ArgumentsNode node);\n\n  void visit(AutoEscapeNode node);\n\n  void visit(BlockNode node);\n\n  void visit(BodyNode node);\n\n  void visit(ExtendsNode node);\n\n  void visit(FlushNode node);\n\n  void visit(ForNode node);\n\n  void visit(IfNode node);\n\n  void visit(ImportNode node);\n\n  void visit(IncludeNode node);\n\n  void visit(MacroNode node);\n\n  void visit(NamedArgumentNode node);\n\n  void visit(ParallelNode node);\n\n  void visit(PositionalArgumentNode node);\n\n  void visit(PrintNode node);\n\n  void visit(RootNode node);\n\n  void visit(SetNode node);\n\n  void visit(TextNode node);\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/NodeVisitorFactory.java",
    "content": "package io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\n/**\n * The node visitor factory creates {@link NodeVisitor}s.\n *\n * <p>\n * {@link Extension} can provide own implementation to provide their own {@link NodeVisitor}s.\n *\n * @author Thomas Hunziker\n */\npublic interface NodeVisitorFactory {\n\n  /**\n   * This method creates a new instance of a {@link NodeVisitor}.\n   *\n   * <p>\n   * The method is called whenever a visitor is applied to a {@link PebbleTemplate}.\n   *\n   * <p>\n   * The method needs to be thread-safe. However the {@link NodeVisitor} itself does not need to be\n   * thread-safe.\n   *\n   * @param template the template for which a visitor should be created for.\n   * @return the visitor.\n   */\n  NodeVisitor createVisitor(PebbleTemplate template);\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/Test.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Map;\n\npublic interface Test extends NamedArguments {\n\n  boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                EvaluationContext context, int\n      lineNumber) throws PebbleException;\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/AbbreviateFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class AbbreviateFilter implements Filter {\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public AbbreviateFilter() {\n    this.argumentNames.add(\"length\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    String value = (String) input;\n    int maxWidth = ((Long) args.get(\"length\")).intValue();\n\n    if (maxWidth < 0) {\n      throw new PebbleException(null,\n          \"Invalid argument to abbreviate filter; must be greater than zero\",\n          lineNumber, self.getName());\n    }\n\n    String ellipsis = \"...\";\n    int length = value.length();\n\n    if (length < maxWidth) {\n      return value;\n    }\n    if (length <= 3) {\n      return value;\n    }\n    if (maxWidth <= 3) {\n      return value.substring(0, maxWidth);\n    }\n    return value.substring(0, Math.max(0, maxWidth - 3)) + ellipsis;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/AbsFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.util.List;\nimport java.util.Map;\n\npublic class AbsFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Number apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber)\n      throws PebbleException {\n    if (input == null) {\n      throw new PebbleException(null, \"Can not pass null value to \\\"abs\\\" filter.\", lineNumber,\n          self.getName());\n    }\n\n    if (input instanceof Integer) {\n      return Math.abs((Integer) input);\n    } else if (input instanceof Byte) {\n      return Math.abs((Byte) input);\n    } else if (input instanceof Short) {\n      return Math.abs((Short) input);\n    } else if (input instanceof Float) {\n      return Math.abs((Float) input);\n    } else if (input instanceof Long) {\n      return Math.abs((Long) input);\n    } else if (input instanceof Double) {\n      return Math.abs((Double) input);\n    } else if (input instanceof BigDecimal) {\n      return ((BigDecimal) input).abs();\n    } else if (input instanceof BigInteger) {\n      return ((BigInteger) input).abs();\n    } else if (input instanceof Number) {\n      // We make here an assumption that we have checked all special\n      // cases.\n      return Math.abs(((Number) input).doubleValue());\n    } else {\n      throw new PebbleException(null, \"The 'abs' filter does require as input a number.\",\n          lineNumber,\n          self.getName());\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/AttributeResolverExtension.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.attributes.AttributeResolver;\nimport io.pebbletemplates.pebble.attributes.DefaultAttributeResolver;\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class AttributeResolverExtension extends AbstractExtension {\n\n  @Override\n  public List<AttributeResolver> getAttributeResolver() {\n    List<AttributeResolver> attributeResolvers = new ArrayList<>();\n    attributeResolvers.add(new DefaultAttributeResolver());\n    return attributeResolvers;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/Base64DecoderFilter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This class implements the 'base64encode' filter.\n *\n * @author Silviu Vergoti\n */\npublic class Base64DecoderFilter implements Filter {\n\n  public static final String FILTER_NAME = \"base64decode\";\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n\n    String decoded = null;\n    if (input instanceof String) {\n      try {\n        byte [] bytes = Base64.getDecoder().decode(((String) input).getBytes(StandardCharsets.UTF_8));\n        decoded = new String(bytes, StandardCharsets.UTF_8);\n      } catch (Exception e) {\n        throw new PebbleException(e, \"Please provide a correctly Base64 encoded string containing an UTF-8 string\\n\", lineNumber, self.getName());\n      }\n    } else {\n      throw new PebbleException(null, \"This filter applies to String\\n\", lineNumber, self.getName());\n    }\n    return decoded;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/Base64EncoderFilter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This class implements the 'base64encode' filter.\n *\n * @author Silviu Vergoti\n */\npublic class Base64EncoderFilter implements Filter {\n\n  public static final String FILTER_NAME = \"base64encode\";\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n\n    return Base64.getEncoder().encodeToString(input.toString().getBytes(StandardCharsets.UTF_8));\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/CapitalizeFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\npublic class CapitalizeFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    String value = (String) input;\n\n    if (value.length() == 0) {\n      return value;\n    }\n\n    StringBuilder result = new StringBuilder();\n\n    char[] chars = value.toCharArray();\n    for (int i = 0; i < chars.length; i++) {\n      char c = chars[i];\n\n      if (Character.isWhitespace(c)) {\n        result.append(c);\n      } else {\n        result.append(Character.toTitleCase(c));\n        result.append(Arrays.copyOfRange(chars, i + 1, chars.length));\n        break;\n      }\n    }\n\n    return result.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/CoreExtension.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.extension.NodeVisitorFactory;\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.node.expression.AddExpression;\nimport io.pebbletemplates.pebble.node.expression.AndExpression;\nimport io.pebbletemplates.pebble.node.expression.ConcatenateExpression;\nimport io.pebbletemplates.pebble.node.expression.ContainsExpression;\nimport io.pebbletemplates.pebble.node.expression.DivideExpression;\nimport io.pebbletemplates.pebble.node.expression.EqualsExpression;\nimport io.pebbletemplates.pebble.node.expression.FilterExpression;\nimport io.pebbletemplates.pebble.node.expression.GreaterThanEqualsExpression;\nimport io.pebbletemplates.pebble.node.expression.GreaterThanExpression;\nimport io.pebbletemplates.pebble.node.expression.LessThanEqualsExpression;\nimport io.pebbletemplates.pebble.node.expression.LessThanExpression;\nimport io.pebbletemplates.pebble.node.expression.ModulusExpression;\nimport io.pebbletemplates.pebble.node.expression.MultiplyExpression;\nimport io.pebbletemplates.pebble.node.expression.NegativeTestExpression;\nimport io.pebbletemplates.pebble.node.expression.NotEqualsExpression;\nimport io.pebbletemplates.pebble.node.expression.OrExpression;\nimport io.pebbletemplates.pebble.node.expression.PositiveTestExpression;\nimport io.pebbletemplates.pebble.node.expression.RangeExpression;\nimport io.pebbletemplates.pebble.node.expression.SubtractExpression;\nimport io.pebbletemplates.pebble.node.expression.UnaryMinusExpression;\nimport io.pebbletemplates.pebble.node.expression.UnaryNotExpression;\nimport io.pebbletemplates.pebble.node.expression.UnaryPlusExpression;\nimport io.pebbletemplates.pebble.operator.Associativity;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.BinaryOperatorImpl;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperatorImpl;\nimport io.pebbletemplates.pebble.operator.*;\nimport io.pebbletemplates.pebble.tokenParser.BlockTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.CacheTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.EmbedTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.ExtendsTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.FilterTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.FlushTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.ForTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.FromTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.IfTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.ImportTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.IncludeTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.MacroTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.ParallelTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.SetTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class CoreExtension extends AbstractExtension {\n\n  @Override\n  public List<TokenParser> getTokenParsers() {\n    List<TokenParser> parsers = new ArrayList<>();\n    parsers.add(new BlockTokenParser());\n    parsers.add(new ExtendsTokenParser());\n    parsers.add(new EmbedTokenParser());\n    parsers.add(new FilterTokenParser());\n    parsers.add(new FlushTokenParser());\n    parsers.add(new ForTokenParser());\n    parsers.add(new IfTokenParser());\n    parsers.add(new ImportTokenParser());\n    parsers.add(new IncludeTokenParser());\n    parsers.add(new MacroTokenParser());\n    parsers.add(new ParallelTokenParser());\n    parsers.add(new SetTokenParser());\n    parsers.add(new CacheTokenParser());\n    parsers.add(new FromTokenParser());\n\n    // verbatim tag is implemented directly in the LexerImpl\n    return parsers;\n  }\n\n  @Override\n  public List<UnaryOperator> getUnaryOperators() {\n    List<UnaryOperator> operators = new ArrayList<>();\n    operators.add(new UnaryOperatorImpl(\"not\", 500, UnaryNotExpression.class));\n    operators.add(new UnaryOperatorImpl(\"+\", 500, UnaryPlusExpression.class));\n    operators.add(new UnaryOperatorImpl(\"-\", 500, UnaryMinusExpression.class));\n    return operators;\n  }\n\n  @Override\n  public List<BinaryOperator> getBinaryOperators() {\n    List<BinaryOperator> operators = new ArrayList<>();\n    operators.add(new BinaryOperatorImpl(\"or\", 10, OrExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"and\", 15, AndExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"is\", 20, PositiveTestExpression::new, BinaryOperatorType.TEST, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"is not\", 20, NegativeTestExpression::new, BinaryOperatorType.TEST, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"contains\", 20, ContainsExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"==\", 30, EqualsExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"equals\", 30, EqualsExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"!=\", 30, NotEqualsExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\">\", 30, GreaterThanExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"<\", 30, LessThanExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\">=\", 30, GreaterThanEqualsExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"<=\", 30, LessThanEqualsExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"+\", 40, AddExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"-\", 40, SubtractExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"*\", 60, MultiplyExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"/\", 60, DivideExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"%\", 60, ModulusExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"|\", 100, FilterExpression::new, BinaryOperatorType.FILTER, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"~\", 110, ConcatenateExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n    operators.add(new BinaryOperatorImpl(\"..\", 120, RangeExpression::new, BinaryOperatorType.NORMAL, Associativity.LEFT));\n\n    return operators;\n  }\n\n  @Override\n  public Map<String, Filter> getFilters() {\n    Map<String, Filter> filters = new HashMap<>();\n    filters.put(\"abbreviate\", new AbbreviateFilter());\n    filters.put(\"abs\", new AbsFilter());\n    filters.put(\"capitalize\", new CapitalizeFilter());\n    filters.put(\"date\", new DateFilter());\n    filters.put(\"default\", new DefaultFilter());\n    filters.put(\"first\", new FirstFilter());\n    filters.put(\"format\", new FormatFilter());\n    filters.put(\"join\", new JoinFilter());\n    filters.put(\"last\", new LastFilter());\n    filters.put(\"lower\", new LowerFilter());\n    filters.put(\"numberformat\", new NumberFormatFilter());\n    filters.put(\"slice\", new SliceFilter());\n    filters.put(\"sort\", new SortFilter());\n    filters.put(\"rsort\", new RsortFilter());\n    filters.put(\"reverse\", new ReverseFilter());\n    filters.put(\"title\", new TitleFilter());\n    filters.put(\"trim\", new TrimFilter());\n    filters.put(\"upper\", new UpperFilter());\n    filters.put(\"urlencode\", new UrlEncoderFilter());\n    filters.put(\"length\", new LengthFilter());\n    filters.put(ReplaceFilter.FILTER_NAME, new ReplaceFilter());\n    filters.put(MergeFilter.FILTER_NAME, new MergeFilter());\n    filters.put(SplitFilter.FILTER_NAME, new SplitFilter());\n    filters.put(Base64EncoderFilter.FILTER_NAME, new Base64EncoderFilter());\n    filters.put(Base64DecoderFilter.FILTER_NAME, new Base64DecoderFilter());\n    filters.put(Sha256Filter.FILTER_NAME, new Sha256Filter());\n    filters.put(Nl2brFilter.FILTER_NAME, new Nl2brFilter());\n    return filters;\n  }\n\n  @Override\n  public Map<String, Test> getTests() {\n    Map<String, Test> tests = new HashMap<>();\n    tests.put(\"empty\", new EmptyTest());\n    tests.put(\"even\", new EvenTest());\n    tests.put(\"iterable\", new IterableTest());\n    tests.put(\"map\", new MapTest());\n    tests.put(\"null\", new NullTest());\n    tests.put(\"odd\", new OddTest());\n    tests.put(\"defined\", new DefinedTest());\n    return tests;\n  }\n\n  @Override\n  public Map<String, Function> getFunctions() {\n    Map<String, Function> functions = new HashMap<>();\n\n    /*\n     * For efficiency purposes, some core functions are individually parsed\n     * by our expression parser and compiled in their own unique way. This\n     * includes the block and parent functions.\n     */\n\n    functions.put(\"max\", new MaxFunction());\n    functions.put(\"min\", new MinFunction());\n    functions.put(RangeFunction.FUNCTION_NAME, new RangeFunction());\n    return functions;\n  }\n\n  @Override\n  public Map<String, Object> getGlobalVariables() {\n    return null;\n  }\n\n  @Override\n  public List<NodeVisitorFactory> getNodeVisitors() {\n    List<NodeVisitorFactory> visitors = new ArrayList<>();\n    visitors.add(new MacroAndBlockRegistrantNodeVisitorFactory());\n    return visitors;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/DateFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.escaper.SafeString;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.time.DateTimeException;\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\nimport java.time.temporal.TemporalAccessor;\nimport java.time.temporal.TemporalQueries;\nimport java.util.*;\n\nimport static java.lang.String.format;\n\npublic class DateFilter implements Filter {\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public DateFilter() {\n    this.argumentNames.add(\"format\");\n    this.argumentNames.add(\"existingFormat\");\n    this.argumentNames.add(\"timeZone\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n    final Locale locale = context.getLocale();\n    final String format = (String) args.get(\"format\");\n    final String timeZone = (String) args.get(\"timeZone\");\n    final String existingFormat = (String) args.get(\"existingFormat\");\n\n    if (TemporalAccessor.class.isAssignableFrom(input.getClass())) {\n      return this.applyTemporal((TemporalAccessor) input, self, locale, lineNumber, format, timeZone);\n    }\n    return this.applyDate(\n            input, self, locale, lineNumber,\n            format, existingFormat, timeZone);\n  }\n\n  private Object applyDate(Object dateOrString, final PebbleTemplate self, final Locale locale,\n      int lineNumber, final String format, final String existingFormatString, final String timeZone)\n      throws PebbleException {\n    Date date;\n    DateFormat intendedFormat;\n\n    if (dateOrString instanceof Date) {\n      date = (Date) dateOrString;\n    } else if (dateOrString instanceof Number) {\n      date = new Date(((Number) dateOrString).longValue());\n    } else if (dateOrString instanceof String) {\n      try {\n        SimpleDateFormat formatter;\n          if (existingFormatString != null){\n            formatter = new SimpleDateFormat(existingFormatString, locale);\n          } else {\n            formatter = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ssZ\", locale);\n          }\n        date = formatter.parse(dateOrString.toString());\n      } catch (Exception e) {\n        String formatTried = existingFormatString != null ? existingFormatString :  \"yyyy-MM-dd'T'HH:mm:ssZ\";\n        throw new PebbleException(e, String.format(\"Could not parse the string '%s' into a date, with formatting: %s\",\n                dateOrString, formatTried), lineNumber, self.getName());\n      }\n    } else {\n      throw new IllegalArgumentException(\n              format(\"Unsupported argument type: %s (value: %s)\", dateOrString.getClass().getName(),\n                      dateOrString));\n    }\n\n    intendedFormat = new SimpleDateFormat(format == null ? \"yyyy-MM-dd'T'HH:mm:ssZ\" : format, locale);\n    if (timeZone != null) {\n      intendedFormat.setTimeZone(TimeZone.getTimeZone(timeZone));\n    }\n    return new SafeString(intendedFormat.format(date));\n  }\n\n  private Object applyTemporal(final TemporalAccessor input, PebbleTemplate self,\n      final Locale locale,\n      int lineNumber, final String format, final String timeZone) throws PebbleException {\n    DateTimeFormatter formatter = format != null\n        ? DateTimeFormatter.ofPattern(format, locale)\n        : DateTimeFormatter.ISO_DATE_TIME;\n\n    ZoneId zoneId = getZoneId(input, timeZone);\n    formatter = formatter.withZone(zoneId);\n\n    try {\n      return new SafeString(formatter.format(input));\n    } catch (DateTimeException dte) {\n      throw new PebbleException(\n              dte,\n              String.format(\"Could not format instance '%s' of type %s into a date.\", input.toString(), input.getClass()),\n              lineNumber,\n              self.getName());\n    }\n  }\n\n  private ZoneId getZoneId(TemporalAccessor input, String timeZone) {\n    // First try the time zone of the input.\n    ZoneId zoneId = input.query(TemporalQueries.zone());\n    if (zoneId == null && timeZone != null) {\n      // Fallback to time zone provided as filter argument.\n      zoneId = ZoneId.of(timeZone);\n    }\n    if (zoneId == null) {\n      // Fallback to system time zone.\n      zoneId = ZoneId.systemDefault();\n    }\n    return zoneId;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/DefaultFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class DefaultFilter implements Filter {\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public DefaultFilter() {\n    this.argumentNames.add(\"default\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n\n    Object defaultObj = args.get(\"default\");\n\n    Test emptyTest = new EmptyTest();\n    if (emptyTest.apply(input, new HashMap<>(), self, context, lineNumber)) {\n      return defaultObj;\n    }\n    return input;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/DefinedTest.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Map;\n\n/**\n * Implementation for the test function 'defined'.\n *\n * Inversion of 'null' test function to provide better compatibility with the original twig version\n * and JTwig.\n *\n * @author Thomas Hunziker\n */\npublic class DefinedTest extends NullTest {\n\n  @Override\n  public boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                       EvaluationContext context, int\n      lineNumber) {\n    return !super.apply(input, args, self, context, lineNumber);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/DisallowExtensionCustomizerBuilder.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Extension;\nimport io.pebbletemplates.pebble.extension.ExtensionCustomizer;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\n\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\n/**\n * @author freshchen\n * @since 2024/3/2\n */\npublic class DisallowExtensionCustomizerBuilder {\n\n  private Collection<String> disallowedFilterKeys;\n\n  private Collection<String> disallowedTokenParserTags;\n\n  private Collection<String> disallowedFunctionKeys;\n\n  private Collection<String> disallowedBinaryOperatorSymbols;\n\n  private Collection<String> disallowedUnaryOperatorSymbols;\n\n  private Collection<String> disallowedTestKeys;\n\n  public DisallowExtensionCustomizerBuilder disallowedFunctionKeys(Collection<String> disallowedFunctionKeys) {\n    this.disallowedFunctionKeys = disallowedFunctionKeys;\n    return this;\n  }\n\n  public DisallowExtensionCustomizerBuilder disallowedTokenParserTags(Collection<String> disallowedTokenParserTags) {\n    this.disallowedTokenParserTags = disallowedTokenParserTags;\n    return this;\n  }\n\n  public DisallowExtensionCustomizerBuilder disallowedFilterKeys(Collection<String> disallowedFilterKeys) {\n    this.disallowedFilterKeys = disallowedFilterKeys;\n    return this;\n  }\n\n  public DisallowExtensionCustomizerBuilder disallowedUnaryOperatorSymbols(Collection<String> disallowedUnaryOperatorSymbols) {\n    this.disallowedUnaryOperatorSymbols = disallowedUnaryOperatorSymbols;\n    return this;\n  }\n\n  public DisallowExtensionCustomizerBuilder disallowedBinaryOperatorSymbols(Collection<String> disallowedBinaryOperatorSymbols) {\n    this.disallowedBinaryOperatorSymbols = disallowedBinaryOperatorSymbols;\n    return this;\n  }\n\n  public DisallowExtensionCustomizerBuilder disallowedTestKeys(Collection<String> disallowedTestKeys) {\n    this.disallowedTestKeys = disallowedTestKeys;\n    return this;\n  }\n\n  public Function<Extension, ExtensionCustomizer> build() {\n\n    return extension -> new ExtensionCustomizer(extension) {\n\n      @Override\n      public Map<String, Test> getTests() {\n        return this.disallow(super::getTests, DisallowExtensionCustomizerBuilder.this.disallowedTestKeys);\n      }\n\n      @Override\n      public List<UnaryOperator> getUnaryOperators() {\n        return this.disallow(super::getUnaryOperators, DisallowExtensionCustomizerBuilder.this.disallowedUnaryOperatorSymbols, UnaryOperator::getSymbol);\n      }\n\n      @Override\n      public List<BinaryOperator> getBinaryOperators() {\n        return this.disallow(super::getBinaryOperators, DisallowExtensionCustomizerBuilder.this.disallowedBinaryOperatorSymbols, BinaryOperator::getSymbol);\n      }\n\n      @Override\n      public Map<String, io.pebbletemplates.pebble.extension.Function> getFunctions() {\n        return this.disallow(super::getFunctions, DisallowExtensionCustomizerBuilder.this.disallowedFunctionKeys);\n      }\n\n      @Override\n      public Map<String, Filter> getFilters() {\n        return this.disallow(super::getFilters, DisallowExtensionCustomizerBuilder.this.disallowedFilterKeys);\n      }\n\n      @Override\n      public List<TokenParser> getTokenParsers() {\n        return this.disallow(super::getTokenParsers, DisallowExtensionCustomizerBuilder.this.disallowedTokenParserTags, TokenParser::getTag);\n      }\n\n      private <T> List<T> disallow(Supplier<List<T>> superGetter,\n                                   Collection<String> disallowedList,\n                                   Function<T, String> keyGetter) {\n        List<T> superList = superGetter.get();\n        if (disallowedList == null || disallowedList.isEmpty()) {\n          return superList;\n        }\n\n        List<T> result = Optional.ofNullable(superList).map(ArrayList::new)\n                .orElseGet(ArrayList::new);\n\n        disallowedList.stream()\n                .filter(Objects::nonNull)\n                .forEach(v -> result.removeIf(t -> v.equals(keyGetter.apply(t))));\n\n        return result;\n      }\n\n      private <T> Map<String, T> disallow(Supplier<Map<String, T>> superGetter,\n                                          Collection<String> disallowedList) {\n        Map<String, T> superMap = superGetter.get();\n        if (disallowedList == null || disallowedList.isEmpty()) {\n          return superMap;\n        }\n\n        Map<String, T> result = Optional.ofNullable(superMap).map(HashMap::new)\n                .orElseGet(HashMap::new);\n\n        disallowedList.stream()\n                .filter(Objects::nonNull)\n                .forEach(result::remove);\n\n        return result;\n      }\n\n    };\n  }\n\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/EmptyTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\npublic class EmptyTest implements Test {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                       EvaluationContext context, int lineNumber) {\n    boolean isEmpty = input == null;\n\n    if (!isEmpty && input instanceof String) {\n      String value = (String) input;\n      isEmpty = \"\".equals(value.trim());\n    }\n\n    if (!isEmpty && input instanceof Collection) {\n      isEmpty = ((Collection<?>) input).isEmpty();\n    }\n\n    if (!isEmpty && input instanceof Map) {\n      isEmpty = ((Map<?, ?>) input).isEmpty();\n    }\n\n    return isEmpty;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/EvenTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class EvenTest implements Test {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                       EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      throw new PebbleException(null, \"Can not pass null value to \\\"even\\\" test.\", lineNumber,\n          self.getName());\n    }\n\n    if (input instanceof Integer) {\n      return ((Integer) input) % 2 == 0;\n    } else {\n      return ((Long) input) % 2 == 0;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/FirstFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.lang.reflect.Array;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Returns the first element of a collection\n *\n * @author mbosecke\n */\npublic class FirstFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n\n    if (input instanceof String) {\n      String inputString = (String) input;\n      return inputString.charAt(0);\n    }\n\n    if (input.getClass().isArray()) {\n      int length = Array.getLength(input);\n      return length > 0 ? Array.get(input, 0) : null;\n    }\n\n    Collection<?> inputCollection = (Collection<?>) input;\n\n    Iterator<?> iterator = inputCollection.iterator();\n    if (iterator.hasNext()) {\n      return iterator.next();\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/FormatFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.IntStream;\n\npublic class FormatFilter implements Filter {\n\n    private final List<String> argumentNames = new ArrayList<>();\n\n    public FormatFilter() {\n    }\n\n    @Override\n    public List<String> getArgumentNames() {\n        return this.argumentNames;\n    }\n\n    @Override\n    public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                        EvaluationContext context, int lineNumber) throws PebbleException {\n\n        if (input == null) {\n            return null;\n        }\n\n        Object[] formatArgs = IntStream.range(0, args.size())\n            .mapToObj(i -> args.get(String.valueOf(i)))\n            .toArray();\n\n        return String.format(input.toString(), formatArgs);\n    }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/IterableTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class IterableTest implements Test {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                       EvaluationContext context, int lineNumber) {\n\n    return input instanceof Iterable || input instanceof Object[];\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/JoinFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Concatenates all entries of a collection, optionally glued together with a particular character\n * such as a comma.\n *\n * @author mbosecke\n */\npublic class JoinFilter implements Filter {\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public JoinFilter() {\n    this.argumentNames.add(\"separator\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber)\n      throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n\n    String glue = null;\n    if (args.containsKey(\"separator\")) {\n      glue = (String) args.get(\"separator\");\n    }\n\n    if (input.getClass().isArray()) {\n      List<Object> items = new ArrayList<>();\n      int length = Array.getLength(input);\n      for (int i = 0; i < length; i++) {\n        items.add(Array.get(input, i));\n      }\n      return this.join(items, glue);\n    } else if (input instanceof Collection) {\n      return this.join((Collection<?>) input, glue);\n    } else {\n      throw new PebbleException(null,\n          \"The 'join' filter expects that the input is either a collection or an array.\",\n          lineNumber,\n          self.getName());\n    }\n  }\n\n  private String join(Collection<?> inputCollection, String glue) {\n    StringBuilder builder = new StringBuilder();\n\n    boolean isFirst = true;\n    for (Object entry : inputCollection) {\n\n      if (!isFirst && glue != null) {\n        builder.append(glue);\n      }\n      builder.append(entry);\n\n      isFirst = false;\n    }\n    return builder.toString();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/LastFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.lang.reflect.Array;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Returns the last element of a collection\n *\n * @author mbosecke\n */\npublic class LastFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n\n    if (input instanceof String) {\n      String inputString = (String) input;\n      return inputString.charAt(inputString.length() - 1);\n    }\n\n    if (input.getClass().isArray()) {\n      int length = Array.getLength(input);\n      return length > 0 ? Array.get(input, length - 1) : null;\n    }\n\n    Collection<?> inputCollection = (Collection<?>) input;\n    Object result = null;\n    for (Object o : inputCollection) {\n      result = o;\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/LengthFilter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.lang.reflect.Array;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\npublic class LengthFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int\n      lineNumber) {\n    if (input == null) {\n      return 0;\n    }\n    if (input instanceof String) {\n      return ((String) input).length();\n    } else if (input instanceof Collection) {\n      return ((Collection<?>) input).size();\n    } else if (input.getClass().isArray()) {\n      return Array.getLength(input);\n    } else if (input instanceof Map) {\n      return ((Map<?, ?>) input).size();\n    } else if (input instanceof Iterable) {\n      Iterator<?> it = ((Iterable<?>) input).iterator();\n      int size = 0;\n      while (it.hasNext()) {\n        it.next();\n        size++;\n      }\n      return size;\n    } else if (input instanceof Iterator) {\n      Iterator<?> it = (Iterator<?>) input;\n      int size = 0;\n      while (it.hasNext()) {\n        it.next();\n        size++;\n      }\n      return size;\n    } else {\n      return 0;\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/LowerFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class LowerFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    if (input instanceof String) {\n      return ((String) input).toLowerCase(context.getLocale());\n    } else {\n      return input.toString().toLowerCase(context.getLocale());\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/MacroAndBlockRegistrantNodeVisitor.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.AbstractNodeVisitor;\nimport io.pebbletemplates.pebble.node.BlockNode;\nimport io.pebbletemplates.pebble.node.MacroNode;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class MacroAndBlockRegistrantNodeVisitor extends AbstractNodeVisitor {\n\n  public MacroAndBlockRegistrantNodeVisitor(PebbleTemplateImpl template) {\n    super(template);\n  }\n\n  @Override\n  public void visit(BlockNode node) {\n    this.getTemplate().registerBlock(node.getBlock());\n    super.visit(node);\n  }\n\n  @Override\n  public void visit(MacroNode node) {\n    try {\n      this.getTemplate().registerMacro(node.getMacro());\n    } catch (PebbleException e) {\n      throw new RuntimeException(e);\n    }\n    super.visit(node);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/MacroAndBlockRegistrantNodeVisitorFactory.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.extension.NodeVisitorFactory;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\n/**\n * Implementation of {@link NodeVisitorFactory} to handle {@link MacroAndBlockRegistrantNodeVisitor}.\n *\n * @author hunziker\n */\npublic class MacroAndBlockRegistrantNodeVisitorFactory implements NodeVisitorFactory {\n\n  @Override\n  public NodeVisitor createVisitor(PebbleTemplate template) {\n    return new MacroAndBlockRegistrantNodeVisitor((PebbleTemplateImpl) template);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/MapTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class MapTest implements Test {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                       EvaluationContext context, int lineNumber) {\n    return input instanceof Map;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/MaxFunction.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\nimport java.util.List;\nimport java.util.Map;\n\npublic class MaxFunction implements Function {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    Object min = null;\n\n    int i = 0;\n\n    while (args.containsKey(String.valueOf(i))) {\n\n      Object candidate = args.get(String.valueOf(i));\n      i++;\n\n      if (min == null) {\n        min = candidate;\n        continue;\n      }\n      if (OperatorUtils.gt(candidate, min)) {\n        min = candidate;\n      }\n\n    }\n    return min;\n\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/MergeFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class MergeFilter implements Filter {\n\n  public static final String FILTER_NAME = \"merge\";\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public MergeFilter() {\n    this.argumentNames.add(\"items\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    Object items = args.get(\"items\");\n    if (input == null) {\n      if (items == null) {\n        throw new PebbleException(null, \"The two arguments to be merged are null\", lineNumber,\n            self.getName());\n      } else {\n        return items;\n      }\n    } else if (items == null) {\n      return input;\n    }\n    // left hand side argument defines resulting type\n    if (input instanceof Map) {\n      return this.mergeAsMap((Map<?, ?>) input, items);\n    } else if (input instanceof List) {\n      return this.mergeAsList((List<?>) input, items, lineNumber, self);\n    } else if (input.getClass().isArray()) {\n      return this.mergeAsArray(input, items, lineNumber, self);\n    } else {\n      throw new PebbleException(null, \"The object being filtered is not a Map/List/Array\",\n          lineNumber, self.getName());\n    }\n  }\n\n  private Object mergeAsMap(Map<?, ?> arg1, Object arg2) {\n    Map<Object, Object> output;\n    if (arg2 instanceof Map) {\n      Map<?, ?> collection2 = (Map<?, ?>) arg2;\n      output = new HashMap<>(arg1.size() + collection2.size() + 16);\n      output.putAll(arg1);\n      output.putAll(collection2);\n    } else if (arg2 instanceof List) {\n      List<?> collection2 = (List<?>) arg2;\n      output = new HashMap<>(arg1.size() + collection2.size() + 16);\n      output.putAll(arg1);\n      for (Object o : collection2) {\n        output.put(o, o);\n      }\n    } else {\n      throw new UnsupportedOperationException(\n          \"Currently, only Maps and Lists can be merged with a Map. Arg2: \" + arg2.getClass()\n              .getName());\n    }\n    return output;\n  }\n\n  private Object mergeAsList(List<?> arg1, Object arg2, int lineNumber, PebbleTemplate self)\n      throws PebbleException {\n    List<Object> output;\n    if (arg2 instanceof Map) {\n      Map<?, ?> collection2 = (Map<?, ?>) arg2;\n      output = new ArrayList<>(arg1.size() + collection2.size() + 16);\n      output.addAll(arg1);\n      output.addAll(collection2.entrySet());\n    } else if (arg2 instanceof List) {\n      List<?> collection2 = (List<?>) arg2;\n      output = new ArrayList<>(arg1.size() + collection2.size() + 16);\n      output.addAll(arg1);\n      output.addAll(collection2);\n    } else {\n      throw new PebbleException(null,\n          \"Currently, only Maps and Lists can be merged with a List. Arg2: \" + arg2.getClass()\n              .getName(), lineNumber, self.getName());\n    }\n    return output;\n  }\n\n  private Object mergeAsArray(Object arg1, Object arg2, int lineNumber, PebbleTemplate self)\n      throws PebbleException {\n    Class<?> arg1Class = arg1.getClass().getComponentType();\n    Class<?> arg2Class = arg2.getClass().getComponentType();\n    if (!arg1Class.equals(arg2Class)) {\n      throw new PebbleException(null,\n          \"Currently, only Arrays of the same component class can be merged. Arg1: \" + arg1Class\n              .getName()\n              + \", Arg2: \" + arg2Class.getName(), lineNumber, self.getName());\n    }\n    Object output = Array.newInstance(arg1Class, Array.getLength(arg1) + Array.getLength(arg2));\n    System.arraycopy(arg1, 0, output, 0, Array.getLength(arg1));\n    System.arraycopy(arg2, 0, output, Array.getLength(arg1), Array.getLength(arg2));\n    return output;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/MinFunction.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\nimport java.util.List;\nimport java.util.Map;\n\npublic class MinFunction implements Function {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    Object min = null;\n\n    int i = 0;\n\n    while (args.containsKey(String.valueOf(i))) {\n\n      Object candidate = args.get(String.valueOf(i));\n      i++;\n\n      if (min == null) {\n        min = candidate;\n        continue;\n      }\n      if (OperatorUtils.lt(candidate, min)) {\n        min = candidate;\n      }\n    }\n    return min;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/Nl2brFilter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class Nl2brFilter implements Filter {\n    public static final String FILTER_NAME = \"nl2br\";\n\n    @Override\n    public Object apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) throws PebbleException {\n        if (input == null) {\n            return null;\n        }\n\n        if (!(input instanceof String)) {\n            throw new IllegalArgumentException(\"nl2br filters only supports String input.\");\n        }\n\n        String strInput = (String) input;\n        if (strInput.indexOf('\\n') == -1 && strInput.indexOf('\\r') == -1) {\n            return strInput;\n        }\n\n        // Pre-size the StringBuilder to be the input length + 16 (to account for some extra <br /> tags)\n        // The 16 is the default size of the StringBuilders default constructor (new StringBuilder())\n        StringBuilder sb = new StringBuilder(strInput.length() + 16);\n\n        return convertNewlinesToBr(strInput, sb);\n    }\n\n    @Override\n    public List<String> getArgumentNames() {\n        return null;\n    }\n\n    private String convertNewlinesToBr(String input, StringBuilder sb) {\n        final int len = input.length();\n        for (int i = 0; i < len; i++) {\n            char c = input.charAt(i);\n            if (c == '\\r') {\n                // Convert CR (possibly part of CRLF) to <br>\n                sb.append(\"<br />\");\n\n                // Skip a following LF to avoid double converting CRLF\n                if (i + 1 < len && input.charAt(i + 1) == '\\n') {\n                    i++; // skip the '\\n'\n                }\n            } else if (c == '\\n') {\n                // Lone LF\n                sb.append(\"<br />\");\n            } else {\n                sb.append(c);\n            }\n        }\n\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/NullTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class NullTest implements Test {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                       EvaluationContext context, int\n      lineNumber) {\n    return input == null;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/NumberFormatFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.text.DecimalFormat;\nimport java.text.DecimalFormatSymbols;\nimport java.text.Format;\nimport java.text.NumberFormat;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\n\npublic class NumberFormatFilter implements Filter {\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public NumberFormatFilter() {\n    this.argumentNames.add(\"format\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n    if (!(input instanceof Number)) {\n      throw new PebbleException(null, \"The input for the 'NumberFormat' filter has to be a number.\",\n          lineNumber, self.getName());\n    }\n\n    Number number = (Number) input;\n\n    Locale locale = context.getLocale();\n\n    if (args.get(\"format\") != null) {\n      Format format = new DecimalFormat((String) args.get(\"format\"),\n          new DecimalFormatSymbols(locale));\n      return format.format(number);\n    } else {\n      NumberFormat numberFormat = NumberFormat.getInstance(locale);\n      return numberFormat.format(number);\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/OddTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class OddTest implements Test {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public boolean apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                       EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      throw new IllegalArgumentException(\"Can not pass null value to \\\"odd\\\" test.\");\n    }\n    EvenTest evenTest = new EvenTest();\n    return !evenTest.apply(input, args, self, context, lineNumber);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/RangeFunction.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Range function to iterate over long or a string with a length of 1.\n *\n * @author Eric Bussieres\n */\npublic class RangeFunction implements Function {\n\n  public static final String FUNCTION_NAME = \"range\";\n\n  private static final String PARAM_END = \"end\";\n\n  private static final String PARAM_INCREMENT = \"increment\";\n\n  private static final String PARAM_START = \"start\";\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public RangeFunction() {\n    this.argumentNames.add(PARAM_START);\n    this.argumentNames.add(PARAM_END);\n    this.argumentNames.add(PARAM_INCREMENT);\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) throws PebbleException {\n    Object start = args.get(PARAM_START);\n    Object end = args.get(PARAM_END);\n    Object increment = args.get(PARAM_INCREMENT);\n    if (increment == null) {\n      increment = 1L;\n    } else if (!(increment instanceof Number)) {\n      throw new PebbleException(null,\n          \"The increment of the range function must be a number \" + increment,\n          lineNumber, self.getName());\n    }\n\n    long incrementNum = ((Number) increment).longValue();\n\n    List<Object> results = new ArrayList<>();\n    // Iterating over Number\n    if (start instanceof Number && end instanceof Number) {\n      long startNum = ((Number) start).longValue();\n      long endNum = ((Number) end).longValue();\n\n      if (incrementNum > 0) {\n        for (long i = startNum; i <= endNum; i += incrementNum) {\n          results.add(i);\n        }\n      } else if (incrementNum < 0) {\n        for (long i = startNum; i >= endNum; i += incrementNum) {\n          results.add(i);\n        }\n      } else {\n        throw new PebbleException(null,\n            \"The increment of the range function must be different than 0\",\n            lineNumber, self.getName());\n      }\n    }\n    // Iterating over character\n    else if (start instanceof String && end instanceof String) {\n      String startStr = (String) start;\n      String endStr = (String) end;\n      if (startStr.length() != 1 || endStr.length() != 1) {\n        throw new PebbleException(null,\n            \"Arguments of range function must be of type Number or String with \"\n                + \"a length of 1\", lineNumber, self.getName());\n      }\n\n      char startChar = startStr.charAt(0);\n      char endChar = endStr.charAt(0);\n\n      if (incrementNum > 0) {\n        for (int i = startChar; i <= endChar; i += incrementNum) {\n          results.add((char) i);\n        }\n      } else if (incrementNum < 0) {\n        for (int i = startChar; i >= endChar; i += incrementNum) {\n          results.add((char) i);\n        }\n      } else {\n        throw new PebbleException(null,\n            \"The increment of the range function must be different than 0\",\n            lineNumber, self.getName());\n      }\n    } else {\n      throw new PebbleException(null,\n          \"Arguments of range function must be of type Number or String with a \"\n              + \"length of 1\", lineNumber, self.getName());\n    }\n\n    return results;\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/ReplaceFilter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.text.MessageFormat;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n/**\n * This class implements the 'replace' filter.\n *\n * @author Thomas Hunziker\n */\npublic class ReplaceFilter implements Filter {\n\n  public static final String FILTER_NAME = \"replace\";\n\n  private static final String ARGUMENT_NAME = \"replace_pairs\";\n\n  private final static List<String> ARGS = Collections.singletonList(ARGUMENT_NAME);\n\n  @Override\n  public List<String> getArgumentNames() {\n    return ARGS;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n    if (args.get(ARGUMENT_NAME) == null) {\n      throw new PebbleException(null,\n          MessageFormat.format(\"The argument ''{0}'' is required.\", ARGUMENT_NAME), lineNumber,\n          self.getName());\n    }\n    Map<?, ?> replacePair = (Map<?, ?>) args.get(ARGUMENT_NAME);\n    String data = input.toString();\n    for (Entry<?, ?> entry : replacePair.entrySet()) {\n      data = data.replace(entry.getKey().toString(), entry.getValue().toString());\n    }\n\n    return data;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/ReverseFilter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Revert the order of an input list\n *\n * @author Andrea La Scola\n */\npublic class ReverseFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    List collection = (List) input;\n    Collections.reverse(collection);\n    return collection;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/RsortFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Sort list items in the reverse order\n *\n * @author Barakat Soror\n */\npublic class RsortFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n  @Override\n  public List<Comparable> apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                                EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    List<Comparable> collection = (List<Comparable>) input;\n    collection.sort(Collections.reverseOrder());\n    return collection;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/Sha256Filter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This class implements the 'sha256' filter.\n *\n * @author Silviu Vergoti\n */\npublic class Sha256Filter implements Filter {\n\n  public static final String FILTER_NAME = \"sha256\";\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n\n    if (input instanceof String) {\n      MessageDigest digest = null;\n      byte[] encodedHash = null;\n      try {\n        digest = MessageDigest.getInstance(\"SHA-256\");\n        encodedHash = digest.digest(((String) input).getBytes(StandardCharsets.UTF_8));\n      } catch (Exception e) {\n        throw new PebbleException(e, \"Hashing exception encountered\\n\", lineNumber, self.getName());\n      }\n      return bytesToHex(encodedHash);\n    } else {\n      throw new PebbleException(null, \"Need a string to hash\\n\", lineNumber, self.getName());\n    }\n  }\n\n  private static String bytesToHex(byte[] bytes) {\n    StringBuilder hexString = new StringBuilder(2 * bytes.length);\n    for (int i = 0; i < bytes.length; i++) {\n      String hex = Integer.toHexString(0xff & bytes[i]);\n      if (hex.length() == 1) {\n        hexString.append('0');\n      }\n      hexString.append(hex);\n    }\n    return hexString.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/SliceFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\npublic class SliceFilter implements Filter {\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public SliceFilter() {\n    this.argumentNames.add(\"fromIndex\");\n    this.argumentNames.add(\"toIndex\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n\n    if (input == null) {\n      return null;\n    }\n\n    // argument parsing\n    Object argFrom = args.get(\"fromIndex\");\n\n    if (argFrom == null) {\n      // defaults to 0\n      argFrom = 0;\n    } else if (!(argFrom instanceof Number)) {\n      throw new PebbleException(null, \"Argument fromIndex must be a number. Actual type: \"\n          + argFrom.getClass().getName(), lineNumber, self.getName());\n    }\n    int from = ((Number) argFrom).intValue();\n    if (from < 0) {\n      throw new IllegalArgumentException(\"fromIndex must be greater than 0\");\n    }\n\n    Object argTo = args.get(\"toIndex\");\n\n    if (argTo == null) {\n      // defaults to input length\n      // argTo == null;\n    } else if (!(argTo instanceof Number)) {\n      throw new PebbleException(null, \"Argument toIndex must be a number. Actual type: \"\n          + argTo.getClass().getName(), lineNumber, self.getName());\n    }\n\n    int length;\n    if (input instanceof List) {\n      length = ((List<?>) input).size();\n    } else if (input.getClass().isArray()) {\n      length = Array.getLength(input);\n    } else if (input instanceof String) {\n      length = ((String) input).length();\n    } else {\n      throw new PebbleException(null,\n          \"Slice filter can only be applied to String, List and array inputs. Actual type was: \"\n              + input.getClass().getName(), lineNumber, self.getName());\n    }\n    int to;\n\n    if (argTo != null) {\n      to = ((Number) argTo).intValue();\n      if (to > length) {\n        throw new PebbleException(null, \"toIndex must be smaller than input size: \" + length,\n            lineNumber, self.getName());\n      } else if (from >= to) {\n        throw new PebbleException(null, \"toIndex must be greater than fromIndex\", lineNumber,\n            self.getName());\n      }\n    } else {\n      to = length;\n    }\n\n    // slice input\n    if (input instanceof List) {\n      List<?> value = (List<?>) input;\n      // FIXME maybe sublist() is not the best option due to its\n      // implementation?\n      return value.subList(from, to);\n    } else if (input.getClass().isArray()) {\n      return sliceArray(input, from, to);\n    } else {\n      String value = (String) input;\n      return value.substring(from, to);\n    }\n  }\n\n  private static Object sliceArray(Object input, int from, int to) {\n    if (input instanceof Object[]) {\n      return Arrays.copyOfRange((Object[]) input, from, to);\n    } else if (input instanceof boolean[]) {\n      return Arrays.copyOfRange((boolean[]) input, from, to);\n    } else if (input instanceof byte[]) {\n      return Arrays.copyOfRange((byte[]) input, from, to);\n    } else if (input instanceof char[]) {\n      return Arrays.copyOfRange((char[]) input, from, to);\n    } else if (input instanceof double[]) {\n      return Arrays.copyOfRange((double[]) input, from, to);\n    } else if (input instanceof float[]) {\n      return Arrays.copyOfRange((float[]) input, from, to);\n    } else if (input instanceof int[]) {\n      return Arrays.copyOfRange((int[]) input, from, to);\n    } else if (input instanceof long[]) {\n      return Arrays.copyOfRange((long[]) input, from, to);\n    } else {\n      return Arrays.copyOfRange((short[]) input, from, to);\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/SortFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\npublic class SortFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n  @Override\n  public List<Comparable> apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                                EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n\n    List<Comparable> collection;\n    if (input instanceof List) {\n      collection = (List<Comparable>) input;\n    } else if (input instanceof Comparable[]) {\n      collection = Arrays.asList((Comparable[]) input);\n    } else {\n      throw new PebbleException(null, \"Unsupported input type for sort filter\", lineNumber,\n          self.getName());\n    }\n    Collections.sort(collection);\n    return collection;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/SplitFilter.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class SplitFilter implements Filter {\n\n  public static final String FILTER_NAME = \"split\";\n  private static final String ARGUMENT_NAME_DELIMITER = \"delimiter\";\n  private static final String ARGUMENT_NAME_LIMIT = \"limit\";\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public SplitFilter() {\n    this.argumentNames.add(ARGUMENT_NAME_DELIMITER);\n    this.argumentNames.add(ARGUMENT_NAME_LIMIT);\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n\n    String delimiter = (String) args.get(ARGUMENT_NAME_DELIMITER);\n    Number limit = (Number) args.get(ARGUMENT_NAME_LIMIT);\n    if (delimiter == null) {\n      throw new PebbleException(null, \"missing delimiter parameter in split filter\", lineNumber,\n          self.getName());\n    }\n\n    if (limit == null) {\n      return ((String) input).split(delimiter);\n    }\n    return ((String) input).split(delimiter, limit.intValue());\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/TitleFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class TitleFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    String value = (String) input;\n\n    if (value.length() == 0) {\n      return value;\n    }\n\n    StringBuilder result = new StringBuilder();\n\n    boolean capitalizeNextCharacter = true;\n\n    for (char c : value.toCharArray()) {\n      if (Character.isWhitespace(c)) {\n        capitalizeNextCharacter = true;\n      } else if (capitalizeNextCharacter) {\n        c = Character.toTitleCase(c);\n        capitalizeNextCharacter = false;\n      }\n      result.append(c);\n    }\n\n    return result.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/TrimFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class TrimFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    String str = (String) input;\n    return str.trim();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/UpperFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class UpperFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    if (input instanceof String) {\n      return ((String) input).toUpperCase(context.getLocale());\n    } else {\n      return input.toString().toUpperCase(context.getLocale());\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/core/UrlEncoderFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.List;\nimport java.util.Map;\n\npublic class UrlEncoderFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    String arg = (String) input;\n    try {\n      arg = URLEncoder.encode(arg, \"UTF-8\");\n    } catch (UnsupportedEncodingException e) {\n    }\n    return arg;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/debug/DebugExtension.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.debug;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.NodeVisitorFactory;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class DebugExtension extends AbstractExtension {\n\n  private final PrettyPrintNodeVisitorFactory prettyPrinter = new PrettyPrintNodeVisitorFactory();\n\n  public List<NodeVisitorFactory> getNodeVisitors() {\n    List<NodeVisitorFactory> visitors = new ArrayList<>();\n    visitors.add(this.prettyPrinter);\n    return visitors;\n  }\n\n  public String toString() {\n    return this.prettyPrinter.toString();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/debug/PrettyPrintNodeVisitor.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.debug;\n\nimport io.pebbletemplates.pebble.extension.AbstractNodeVisitor;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.BlockNode;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.FlushNode;\nimport io.pebbletemplates.pebble.node.ForNode;\nimport io.pebbletemplates.pebble.node.IfNode;\nimport io.pebbletemplates.pebble.node.ImportNode;\nimport io.pebbletemplates.pebble.node.IncludeNode;\nimport io.pebbletemplates.pebble.node.NamedArgumentNode;\nimport io.pebbletemplates.pebble.node.Node;\nimport io.pebbletemplates.pebble.node.ParallelNode;\nimport io.pebbletemplates.pebble.node.PrintNode;\nimport io.pebbletemplates.pebble.node.RootNode;\nimport io.pebbletemplates.pebble.node.SetNode;\nimport io.pebbletemplates.pebble.node.TestInvocationExpression;\nimport io.pebbletemplates.pebble.node.TextNode;\nimport io.pebbletemplates.pebble.node.expression.BinaryExpression;\nimport io.pebbletemplates.pebble.node.expression.ContextVariableExpression;\nimport io.pebbletemplates.pebble.node.expression.FilterInvocationExpression;\nimport io.pebbletemplates.pebble.node.expression.FunctionOrMacroInvocationExpression;\nimport io.pebbletemplates.pebble.node.expression.GetAttributeExpression;\nimport io.pebbletemplates.pebble.node.expression.ParentFunctionExpression;\nimport io.pebbletemplates.pebble.node.expression.TernaryExpression;\nimport io.pebbletemplates.pebble.node.expression.UnaryExpression;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class PrettyPrintNodeVisitor extends AbstractNodeVisitor {\n\n  public PrettyPrintNodeVisitor(PebbleTemplateImpl template) {\n    super(template);\n  }\n\n  private StringBuilder output = new StringBuilder();\n\n  private int level = 0;\n\n  private void write(String message) {\n    for (int i = 0; i < this.level - 1; i++) {\n      this.output.append(\"| \");\n    }\n    if (this.level > 0) {\n      this.output.append(\"|-\");\n    }\n    this.output.append(message.toUpperCase()).append(\"\\n\");\n  }\n\n  public String toString() {\n    return this.output.toString();\n  }\n\n  /**\n   * Default method used for unknown nodes such as nodes from a user provided extension.\n   */\n  @Override\n  public void visit(Node node) {\n    this.write(\"unknown\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(BodyNode node) {\n    this.write(\"body\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(IfNode node) {\n    this.write(\"if\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(ForNode node) {\n    this.write(\"for\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(BinaryExpression<?> node) {\n    this.write(\"binary\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(UnaryExpression node) {\n    this.write(\"unary\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(ContextVariableExpression node) {\n    this.write(String.format(\"context variable [%s]\", node.getName()));\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(FilterInvocationExpression node) {\n    this.write(\"filter\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(FunctionOrMacroInvocationExpression node) {\n    this.write(\"function or macro\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(GetAttributeExpression node) {\n    this.write(\"get attribute\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(NamedArgumentNode node) {\n    this.write(\"named argument\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(ArgumentsNode node) {\n    this.write(\"named arguments\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(ParentFunctionExpression node) {\n    this.write(\"parent function\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(TernaryExpression node) {\n    this.write(\"ternary\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  public void visit(TestInvocationExpression node) {\n    this.write(\"test\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(BlockNode node) {\n    this.write(String.format(\"block [%s]\", node.getName()));\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(FlushNode node) {\n    this.write(\"flush\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(ImportNode node) {\n    this.write(\"import\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(IncludeNode node) {\n    this.write(\"include\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(ParallelNode node) {\n    this.write(\"parallel\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(PrintNode node) {\n    this.write(\"print\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(RootNode node) {\n    this.write(\"root\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(SetNode node) {\n    this.write(\"set\");\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n\n  @Override\n  public void visit(TextNode node) {\n    String text = new String(node.getData());\n    String preview = text.length() > 10 ? text.substring(0, 10) + \"...\" : text;\n    this.write(String.format(\"text [%s]\", preview));\n    this.level++;\n    super.visit(node);\n    this.level--;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/debug/PrettyPrintNodeVisitorFactory.java",
    "content": "package io.pebbletemplates.pebble.extension.debug;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.extension.NodeVisitorFactory;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\n/**\n * Implementation of {@link NodeVisitorFactory} to create {@link PrettyPrintNodeVisitor}.\n */\npublic class PrettyPrintNodeVisitorFactory implements NodeVisitorFactory {\n\n  @Override\n  public NodeVisitor createVisitor(PebbleTemplate template) {\n    return new PrettyPrintNodeVisitor((PebbleTemplateImpl) template);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/escaper/EscapeFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.escaper;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.utils.StringUtils;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.unbescape.css.CssEscape;\nimport org.unbescape.html.HtmlEscape;\nimport org.unbescape.javascript.JavaScriptEscape;\nimport org.unbescape.json.JsonEscape;\nimport org.unbescape.uri.UriEscape;\n\npublic class EscapeFilter implements Filter {\n\n  public static final String HTML_ESCAPE_STRATEGY = \"html\";\n  public static final String JAVASCRIPT_ESCAPE_STRATEGY = \"js\";\n  public static final String CSS_ESCAPE_STRATEGY = \"css\";\n  public static final String URL_PARAM_ESCAPE_STRATEGY = \"url_param\";\n  public static final String JSON_ESCAPE_STRATEGY = \"json\";\n\n  private String defaultStrategy = HTML_ESCAPE_STRATEGY;\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  private final Map<String, EscapingStrategy> strategies = new HashMap<>();\n\n  public EscapeFilter() {\n    this.buildDefaultStrategies();\n    this.argumentNames.add(\"strategy\");\n  }\n\n  private void buildDefaultStrategies() {\n    this.strategies.put(HTML_ESCAPE_STRATEGY, HtmlEscape::escapeHtml4Xml);\n    this.strategies.put(JAVASCRIPT_ESCAPE_STRATEGY, JavaScriptEscape::escapeJavaScript);\n    this.strategies.put(CSS_ESCAPE_STRATEGY, CssEscape::escapeCssIdentifier);\n    this.strategies.put(URL_PARAM_ESCAPE_STRATEGY, UriEscape::escapeUriQueryParam);\n    this.strategies.put(JSON_ESCAPE_STRATEGY, JsonEscape::escapeJson);\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object apply(Object inputObject, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (inputObject == null || inputObject instanceof SafeString) {\n      return inputObject;\n    }\n    String input = StringUtils.toString(inputObject);\n\n    String strategy = this.defaultStrategy;\n\n    if (args.get(\"strategy\") != null) {\n      strategy = (String) args.get(\"strategy\");\n    }\n\n    if (!this.strategies.containsKey(strategy)) {\n      throw new PebbleException(null, String.format(\"Unknown escaping strategy [%s]\", strategy),\n          lineNumber, self.getName());\n    }\n\n    return new SafeString(this.strategies.get(strategy).escape(input));\n  }\n\n  public String getDefaultStrategy() {\n    return this.defaultStrategy;\n  }\n\n  public void setDefaultStrategy(String defaultStrategy) {\n    this.defaultStrategy = defaultStrategy;\n  }\n\n  public void addEscapingStrategy(String name, EscapingStrategy strategy) {\n    this.strategies.put(name, strategy);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/escaper/EscaperExtension.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.escaper;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.NodeVisitorFactory;\nimport io.pebbletemplates.pebble.tokenParser.AutoEscapeTokenParser;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class EscaperExtension extends AbstractExtension {\n\n  private final EscapeFilter filter;\n\n  private final EscaperNodeVisitorFactory visitorFactory;\n\n  public EscaperExtension() {\n    this.filter = new EscapeFilter();\n    this.visitorFactory = new EscaperNodeVisitorFactory();\n  }\n\n  @Override\n  public Map<String, Filter> getFilters() {\n    Map<String, Filter> filters = new HashMap<>();\n    filters.put(\"escape\", this.filter);\n    filters.put(\"raw\", new RawFilter());\n    return filters;\n  }\n\n  @Override\n  public List<TokenParser> getTokenParsers() {\n    List<TokenParser> parsers = new ArrayList<>();\n    parsers.add(new AutoEscapeTokenParser());\n    return parsers;\n  }\n\n  @Override\n  public List<NodeVisitorFactory> getNodeVisitors() {\n    List<NodeVisitorFactory> visitors = new ArrayList<>();\n    visitors.add(this.visitorFactory);\n    return visitors;\n  }\n\n  /**\n   * Sets the default escaping strategy.\n   *\n   * @param strategy Escaping strategy\n   */\n  public void setDefaultStrategy(String strategy) {\n    // TODO: This method is dangerous, because the state of the filter is\n    // changed. When this is changed during the rendering of template this\n    // can lead to unexpected results.\n    this.filter.setDefaultStrategy(strategy);\n  }\n\n  public void setAutoEscaping(boolean auto) {\n    this.visitorFactory.setAutoEscaping(auto);\n  }\n\n  /**\n   * Adds a custom escaping strategy to the filter.\n   *\n   * @param name Name of the escaping strategy\n   * @param strategy The implementation of the escaping strategy\n   */\n  public void addEscapingStrategy(String name, EscapingStrategy strategy) {\n    // TODO: This method is dangerous, because the state of the filter is\n    // changed. When this is changed during the rendering of template this\n    // can lead to unexpected results.\n    this.filter.addEscapingStrategy(name, strategy);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/escaper/EscaperNodeVisitor.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.escaper;\n\nimport io.pebbletemplates.pebble.extension.AbstractNodeVisitor;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.AutoEscapeNode;\nimport io.pebbletemplates.pebble.node.NamedArgumentNode;\nimport io.pebbletemplates.pebble.node.PrintNode;\nimport io.pebbletemplates.pebble.node.expression.BlockFunctionExpression;\nimport io.pebbletemplates.pebble.node.expression.ConcatenateExpression;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.expression.FilterExpression;\nimport io.pebbletemplates.pebble.node.expression.FilterInvocationExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralStringExpression;\nimport io.pebbletemplates.pebble.node.expression.ParentFunctionExpression;\nimport io.pebbletemplates.pebble.node.expression.TernaryExpression;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\n\npublic class EscaperNodeVisitor extends AbstractNodeVisitor {\n\n  private final LinkedList<String> strategies = new LinkedList<>();\n\n  private final LinkedList<Boolean> active = new LinkedList<>();\n\n  public EscaperNodeVisitor(PebbleTemplateImpl template, boolean autoEscapting) {\n    super(template);\n    this.pushAutoEscapeState(autoEscapting);\n  }\n\n  @Override\n  public void visit(PrintNode node) {\n    Expression<?> expression = node.getExpression();\n\n    if (expression instanceof TernaryExpression) {\n      TernaryExpression ternary = (TernaryExpression) expression;\n      Expression<?> left = ternary.getExpression2();\n      Expression<?> right = ternary.getExpression3();\n      if (this.isUnsafe(left)) {\n        ternary.setExpression2(this.escape(left));\n      }\n      if (this.isUnsafe(right)) {\n        ternary.setExpression3(this.escape(right));\n      }\n    } else {\n      if (this.isUnsafe(expression)) {\n        node.setExpression(this.escape(expression));\n      }\n    }\n  }\n\n  @Override\n  public void visit(AutoEscapeNode node) {\n    this.active.push(node.isActive());\n    this.strategies.push(node.getStrategy());\n\n    node.getBody().accept(this);\n\n    this.active.pop();\n    this.strategies.pop();\n  }\n\n  /**\n   * Simply wraps the input expression with a {@link EscapeFilter}.\n   */\n  private Expression<?> escape(Expression<?> expression) {\n\n    /*\n     * Build the arguments to the escape filter. The arguments will just\n     * include the strategy being used.\n     */\n    List<NamedArgumentNode> namedArgs = new ArrayList<>();\n    if (!this.strategies.isEmpty() && this.strategies.peek() != null) {\n      String strategy = this.strategies.peek();\n      namedArgs.add(new NamedArgumentNode(\"strategy\",\n          new LiteralStringExpression(strategy, expression.getLineNumber())));\n    }\n    ArgumentsNode args = new ArgumentsNode(null, namedArgs, expression.getLineNumber());\n\n    /*\n     * Create the filter invocation with the newly created named arguments.\n     */\n    FilterInvocationExpression filter = new FilterInvocationExpression(\"escape\", args,\n        expression.getLineNumber());\n\n    /*\n     * The given expression and the filter invocation now become a binary\n     * expression which is what is returned.\n     */\n    FilterExpression binary = new FilterExpression();\n    binary.setLeft(expression);\n    binary.setRight(filter);\n    return binary;\n  }\n\n  private boolean isUnsafe(Expression<?> expression) {\n\n    // check whether the autoescaper is even active\n    if (this.active.peek() == Boolean.FALSE) {\n      return false;\n    }\n\n    boolean unsafe = true;\n\n    // string literals are safe\n    if (expression instanceof LiteralStringExpression) {\n      unsafe = false;\n    } else if (expression instanceof ParentFunctionExpression\n        || expression instanceof BlockFunctionExpression) {\n      unsafe = false;\n    } else if (this.isSafeConcatenateExpr(expression)) {\n      unsafe = false;\n    }\n\n    return unsafe;\n  }\n\n  /**\n   * Returns true if {@code expr} is a {@link ConcatenateExpression} made up of two {@link\n   * LiteralStringExpression}s.\n   */\n  private boolean isSafeConcatenateExpr(Expression<?> expr) {\n    if (!(expr instanceof ConcatenateExpression)) {\n      return false;\n    }\n    ConcatenateExpression cexpr = (ConcatenateExpression) expr;\n    return cexpr.getLeftExpression() instanceof LiteralStringExpression\n        && cexpr.getRightExpression() instanceof LiteralStringExpression;\n  }\n\n  public void pushAutoEscapeState(boolean auto) {\n    this.active.push(auto);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/escaper/EscaperNodeVisitorFactory.java",
    "content": "package io.pebbletemplates.pebble.extension.escaper;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.extension.NodeVisitorFactory;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\n/**\n * Factory class for creating {@link EscaperNodeVisitor}.\n *\n * @author Thomas Hunziker\n */\npublic class EscaperNodeVisitorFactory implements NodeVisitorFactory {\n\n  private boolean autoEscaping = true;\n\n  @Override\n  public NodeVisitor createVisitor(PebbleTemplate template) {\n    return new EscaperNodeVisitor((PebbleTemplateImpl) template, this.autoEscaping);\n  }\n\n  public void setAutoEscaping(boolean auto) {\n    autoEscaping = auto;\n  }\n\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/escaper/EscapingStrategy.java",
    "content": "package io.pebbletemplates.pebble.extension.escaper;\n\npublic interface EscapingStrategy {\n\n  String escape(String input);\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/escaper/RawFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.escaper;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\npublic class RawFilter implements Filter {\n\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object apply(Object inputObject, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    return inputObject == null ? null : new SafeString(inputObject.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/escaper/SafeString.java",
    "content": "package io.pebbletemplates.pebble.extension.escaper;\n\n/**\n * Wrap a string in this to mark the string as safe to ignore by the Escape extension.\n *\n * <p>\n * <b>Warning:</b> The EscaperExtension will never escape a string that is wrapped with this class.\n */\npublic class SafeString {\n\n  private final String content;\n\n  public SafeString(String content) {\n    this.content = content;\n  }\n\n  @Override\n  public String toString() {\n    return this.content;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    return o instanceof SafeString && this.content.equals(((SafeString) o).content);\n  }\n\n  @Override\n  public int hashCode() {\n    return (this.content == null) ? 0 : this.content.hashCode();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/i18n/I18nExtension.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.i18n;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Function;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class I18nExtension extends AbstractExtension {\n\n  @Override\n  public Map<String, Function> getFunctions() {\n    Map<String, Function> functions = new HashMap<>();\n    functions.put(\"i18n\", new i18nFunction());\n    return functions;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/i18n/UTF8Control.java",
    "content": "package io.pebbletemplates.pebble.extension.i18n;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Locale;\nimport java.util.PropertyResourceBundle;\nimport java.util.ResourceBundle;\n\npublic class UTF8Control extends ResourceBundle.Control {\n\n  @Override\n  public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader,\n      boolean reload)\n      throws IOException {\n\n    String bundleName = toBundleName(baseName, locale);\n    String resourceName = toResourceName(bundleName, \"properties\");\n    ResourceBundle bundle = null;\n    InputStream stream = null;\n    if (reload) {\n      URL url = loader.getResource(resourceName);\n      if (url != null) {\n        URLConnection connection = url.openConnection();\n        if (connection != null) {\n          connection.setUseCaches(false);\n          stream = connection.getInputStream();\n        }\n      }\n    } else {\n      stream = loader.getResourceAsStream(resourceName);\n    }\n\n    if (stream != null) {\n      try {\n        bundle = new PropertyResourceBundle(new InputStreamReader(stream, StandardCharsets.UTF_8));\n      } finally {\n        stream.close();\n      }\n    }\n    return bundle;\n  }\n}"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/i18n/i18nFunction.java",
    "content": "/*\n * This file is part of Pebble.\n * <p/>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p/>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension.i18n;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.text.MessageFormat;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.ResourceBundle;\n\npublic class i18nFunction implements Function {\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  public i18nFunction() {\n    this.argumentNames.add(\"bundle\");\n    this.argumentNames.add(\"key\");\n    this.argumentNames.add(\"params\");\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String basename = (String) args.get(\"bundle\");\n    String key = (String) args.get(\"key\");\n    Object params = args.get(\"params\");\n\n    Locale locale = context.getLocale();\n\n    ResourceBundle bundle = ResourceBundle.getBundle(basename, locale, new UTF8Control());\n    Object phraseObject = bundle.getObject(key);\n\n    if (params != null) {\n      if (params instanceof List) {\n        List<?> list = (List<?>) params;\n        return MessageFormat.format(phraseObject.toString(), list.toArray());\n      } else {\n        return MessageFormat.format(phraseObject.toString(), params);\n      }\n    }\n\n    return phraseObject;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/writer/PooledSpecializedStringWriter.java",
    "content": "package io.pebbletemplates.pebble.extension.writer;\n\nimport java.io.Writer;\n\n/**\n * A ${@link SpecializedWriter} that's pooled in a ${@link ThreadLocal}. It's backed by a ${@link\n * StringBuilder} so it's not threadsafe but doesn't involve synchronization. Beware that it has\n * some limitations:\n * <ul>\n * <li>As it's backed by a ${@link ThreadLocal}, it might leak in environments where ClassLoaders\n * are rebooted at runtime</li>\n * <li>It doesn't take any security measure against very large payloads that would cause underlying\n * buffers to eat memory</li>\n * </ul>\n */\npublic class PooledSpecializedStringWriter extends Writer implements SpecializedWriter {\n\n  private static final ThreadLocal<PooledSpecializedStringWriter> POOL = ThreadLocal\n      .withInitial(PooledSpecializedStringWriter::new);\n\n  private StringBuilder sb = new StringBuilder();\n\n  private PooledSpecializedStringWriter() {\n  }\n\n  @Override\n  public void writeSpecialized(int i) {\n    sb.append(i);\n  }\n\n  @Override\n  public void writeSpecialized(long l) {\n    sb.append(l);\n  }\n\n  @Override\n  public void writeSpecialized(double d) {\n    sb.append(d);\n  }\n\n  @Override\n  public void writeSpecialized(float f) {\n    sb.append(f);\n  }\n\n  @Override\n  public void writeSpecialized(short s) {\n    sb.append(s);\n  }\n\n  @Override\n  public void writeSpecialized(byte b) {\n    sb.append(b);\n  }\n\n  @Override\n  public void writeSpecialized(char c) {\n    sb.append(c);\n  }\n\n  @Override\n  public void writeSpecialized(String s) {\n    sb.append(s);\n  }\n\n  @Override\n  public void write(char[] cbuf, int off, int len) {\n    sb.append(cbuf, off, len);\n  }\n\n  @Override\n  public void flush() {\n  }\n\n  @Override\n  public void close() {\n  }\n\n  @Override\n  public String toString() {\n    return sb.toString();\n  }\n\n  public static PooledSpecializedStringWriter pooled() {\n    PooledSpecializedStringWriter pooled = POOL.get();\n    pooled.sb.setLength(0);\n    return pooled;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/writer/SpecializedWriter.java",
    "content": "package io.pebbletemplates.pebble.extension.writer;\n\nimport java.math.BigDecimal;\n\n/**\n * A special type to be implemented by ${@link java.io.Writer}s so Pebble can bypass ${@link\n * Number}s String allocation and directly write primitives.\n */\npublic interface SpecializedWriter {\n\n  void writeSpecialized(int i);\n\n  void writeSpecialized(long l);\n\n  void writeSpecialized(double d);\n\n  void writeSpecialized(float f);\n\n  void writeSpecialized(short s);\n\n  void writeSpecialized(byte b);\n\n  void writeSpecialized(char c);\n\n  void writeSpecialized(String s);\n\n  default void write(Object o) {\n    if (o == null) {\n      throw new IllegalArgumentException(\"Var can not be null\");\n    } else if (o instanceof String) {\n      writeSpecialized((String) o);\n    } else if (o instanceof Integer) {\n      writeSpecialized(((Integer) o).intValue());\n    } else if (o instanceof Long) {\n      writeSpecialized(((Long) o).longValue());\n    } else if (o instanceof Double) {\n      writeSpecialized(((Double) o).doubleValue());\n    } else if (o instanceof Float) {\n      writeSpecialized(((Float) o).floatValue());\n    } else if (o instanceof Short) {\n      writeSpecialized(((Short) o).shortValue());\n    } else if (o instanceof Byte) {\n      writeSpecialized(((Byte) o).byteValue());\n    } else if (o instanceof Character) {\n      writeSpecialized(((Character) o).charValue());\n    } else if (o instanceof BigDecimal) {\n      writeSpecialized(((BigDecimal) o).toPlainString());\n    } else {\n      writeSpecialized(o.toString());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/extension/writer/StringWriterSpecializedAdapter.java",
    "content": "package io.pebbletemplates.pebble.extension.writer;\n\nimport java.io.StringWriter;\n\n/**\n * A ${@link SpecializedWriter} that wraps a ${@link StringWriter}. Directly write numbers into the\n * underlying ${@link StringBuffer} and save String allocations (compared to ${@link\n * java.io.Writer}).\n */\npublic class StringWriterSpecializedAdapter implements SpecializedWriter {\n\n  private final StringBuffer buff;\n\n  public StringWriterSpecializedAdapter(StringWriter sw) {\n    this.buff = sw.getBuffer();\n  }\n\n  @Override\n  public void writeSpecialized(int i) {\n    buff.append(i);\n  }\n\n  @Override\n  public void writeSpecialized(long l) {\n    buff.append(l);\n  }\n\n  @Override\n  public void writeSpecialized(double d) {\n    buff.append(d);\n  }\n\n  @Override\n  public void writeSpecialized(float f) {\n    buff.append(f);\n  }\n\n  @Override\n  public void writeSpecialized(short s) {\n    buff.append(s);\n  }\n\n  @Override\n  public void writeSpecialized(byte b) {\n    buff.append(b);\n  }\n\n  @Override\n  public void writeSpecialized(char i) {\n    buff.append(i);\n  }\n\n  @Override\n  public void writeSpecialized(String s) {\n    buff.append(s);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/lexer/Lexer.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.lexer;\n\nimport java.io.Reader;\n\npublic interface Lexer {\n\n  TokenStream tokenize(Reader templateReader, String name);\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/lexer/LexerImpl.java",
    "content": "/*\n * This file is part of Pebble. <p> Copyright (c) 2014 by Mitchell Bösecke <p> For the full\n * copyright and license information, please view the LICENSE file that was distributed with this\n * source code.\n */\npackage io.pebbletemplates.pebble.lexer;\n\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token.Type;\nimport io.pebbletemplates.pebble.utils.Pair;\nimport io.pebbletemplates.pebble.utils.StringLengthComparator;\nimport io.pebbletemplates.pebble.utils.StringUtils;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Deque;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class reads the template input and builds single items out of it.\n * <p>\n * This class is not thread safe.\n */\npublic final class LexerImpl implements Lexer {\n\n  private final Logger logger = LoggerFactory.getLogger(LexerImpl.class);\n\n  /**\n   * Syntax\n   */\n  private final Syntax syntax;\n\n  /**\n   * Unary operators\n   */\n  private final Collection<UnaryOperator> unaryOperators;\n\n  /**\n   * Binary operators\n   */\n  private final Collection<BinaryOperator> binaryOperators;\n\n  /**\n   * As we progress through the source we maintain a string which is the text that has yet to be\n   * tokenized.\n   */\n  private TemplateSource source;\n\n  /**\n   * The list of tokens that we find and use to create a TokenStream\n   */\n  private ArrayList<Token> tokens;\n\n  /**\n   * Represents the brackets we are currently inside ordered by how recently we encountered them.\n   * (i.e. peek() will return the most innermost bracket, getLast() will return the outermost).\n   * Brackets in this case includes double quotes. The String value of the pair is the bracket\n   * representation, and the Integer is the line number.\n   */\n  private LinkedList<Pair<String, Integer>> brackets;\n\n  /**\n   * The state of the lexer is important so that we know what to expect next and to help discover\n   * errors in the template (ex. unclosed comments).\n   */\n  private Deque<State> lexerStateStack = new ArrayDeque<State>();\n\n  private enum State {\n    DATA, EXECUTE, PRINT, COMMENT, STRING, STRING_INTERPOLATION\n  }\n\n  /**\n   * If we encountered an END delimiter that was preceded with a whitespace trim character (ex. {{\n   * foo -}}) then this boolean is toggled to \"true\" which tells the lexData() method to trim\n   * leading whitespace from the next text token.\n   */\n  private boolean trimLeadingWhitespaceFromNextData = false;\n\n  /**\n   * Static regular expressions for identifiers.\n   */\n  private static final Pattern REGEX_IDENTIFIER;\n  \n  static {\n    if (\"The Android Project\".equals(System.getProperty(\"java.vendor\"))) {\n      REGEX_IDENTIFIER = Pattern.compile(\"^[\\\\p{Letter}_][\\\\p{Letter}\\\\p{Digit}_]*\");\n    } else {\n      // Standard\n      REGEX_IDENTIFIER = Pattern.compile(\"^[\\\\p{IsLetter}_][\\\\p{IsLetter}\\\\p{IsDigit}_]*\");\n    }\n  }\n\n  private static final Pattern REGEX_LONG = Pattern.compile(\"^[0-9]+L\");\n\n  private static final Pattern REGEX_NUMBER = Pattern.compile(\"^[0-9]+(\\\\.[0-9]+)?\");\n\n  /**\n   * Matches a double quote\n   */\n  private static final Pattern REGEX_DOUBLEQUOTE = Pattern.compile(\"^\\\"\");\n\n  /**\n   * Matches everything up to the first interpolation in a double quoted string\n   */\n  private static final Pattern REGEX_STRING_NON_INTERPOLATED_PART =\n      Pattern.compile(\"^[^#\\\"\\\\\\\\]*(?:(?:\\\\\\\\.|#(?!\\\\{))[^#\\\"\\\\\\\\]*)*\", Pattern.DOTALL);\n\n  /**\n   * Matches single quoted strings and double quoted strings without interpolation. Extra complexity\n   * is due to ignoring escaped quotation marks.\n   */\n  private static final Pattern REGEX_STRING_PLAIN = Pattern.compile(\n      \"^\\\"([^#\\\"\\\\\\\\]*(?:\\\\\\\\.[^#\\\"\\\\\\\\]*)*)\\\"|'([^'\\\\\\\\]*(?:\\\\\\\\.[^'\\\\\\\\]*)*)'\", Pattern.DOTALL);\n\n  private static final String PUNCTUATION = \"()[]{}?:.,|=\";\n\n  /**\n   * Regular expression to find operators\n   */\n  private Pattern regexOperators;\n\n  /**\n   * Constructor\n   *\n   * @param syntax The primary syntax\n   * @param unaryOperators The available unary operators\n   * @param binaryOperators The available binary operators\n   */\n  public LexerImpl(Syntax syntax, Collection<UnaryOperator> unaryOperators,\n      Collection<BinaryOperator> binaryOperators) {\n    this.syntax = syntax;\n    this.unaryOperators = unaryOperators;\n    this.binaryOperators = binaryOperators;\n  }\n\n  /**\n   * This is the main method used to tokenize the raw contents of a template.\n   *\n   * @param reader The reader provided from the Loader\n   * @param name The name of the template (used for meaningful error messages)\n   */\n  @Override\n  public TokenStream tokenize(Reader reader, String name) {\n\n    // operator regex\n    this.buildOperatorRegex();\n\n    // standardize the character used for line breaks\n    try {\n      this.source = new TemplateSource(reader, name);\n    } catch (IOException e) {\n      throw new ParserException(e, \"Can not convert template Reader into a String\", 0, name);\n    }\n\n\n    this.tokens = new ArrayList<>();\n    this.lexerStateStack = new ArrayDeque<>();\n    this.brackets = new LinkedList<>();\n\n    /*\n     * Start in a DATA state by pushing it to the state stack. This state basically means that we\n     * are NOT in between a pair of meaningful delimiters.\n     */\n    this.lexerStateStack.push(State.DATA);\n\n    /*\n     * loop through the entire source and apply different lexing methods depending on what kind of\n     * state we are in at the time.\n     *\n     * This will always start on lexData();\n     */\n    while (this.source.length() > 0) {\n      switch (this.lexerStateStack.peek()) {\n        case DATA:\n          this.tokenizeData();\n          break;\n        case EXECUTE:\n          this.tokenizeBetweenExecuteDelimiters();\n          break;\n        case PRINT:\n          this.tokenizeBetweenPrintDelimiters();\n          break;\n        case COMMENT:\n          this.tokenizeComment();\n          break;\n        case STRING:\n          this.tokenizeString();\n          break;\n        case STRING_INTERPOLATION:\n          this.tokenizeStringInterpolation();\n          break;\n        default:\n          break;\n      }\n    }\n\n    // end of file token\n    this.pushToken(Token.Type.EOF);\n    this.popState();\n\n    // make sure that all brackets have been closed, else throw an error\n    if (!this.brackets.isEmpty()) {\n      String expected = this.brackets.pop().getLeft();\n      throw new ParserException(null, String.format(\"Unclosed \\\"%s\\\"\", expected),\n          this.source.getLineNumber(), this.source.getFilename());\n    }\n\n    return new TokenStream(this.tokens, this.source.getFilename());\n  }\n\n  private void tokenizeStringInterpolation() {\n    this.logger.trace(\"Tokenizing String Interpolation\");\n    String lastBracket = this.brackets.peek().getLeft();\n    Matcher matcher = this.syntax.getRegexInterpolationClose().matcher(this.source);\n    if (this.syntax.getInterpolationOpenDelimiter().equals(lastBracket) && matcher.lookingAt()) {\n      this.brackets.pop();\n      this.pushToken(Token.Type.STRING_INTERPOLATION_END);\n      this.source.advance(matcher.end());\n      this.popState();\n    } else {\n      this.tokenizeExpression();\n    }\n  }\n\n  private void tokenizeString() {\n    this.logger.trace(\"Tokenizing String\");\n    // interpolation\n    Matcher matcher = this.syntax.getRegexInterpolationOpen().matcher(this.source);\n    if (matcher.lookingAt()) {\n      this.brackets.push(\n          new Pair<>(this.syntax.getInterpolationOpenDelimiter(), this.source.getLineNumber()));\n      this.pushToken(Token.Type.STRING_INTERPOLATION_START);\n      this.source.advance(matcher.end());\n      this.lexerStateStack.push(State.STRING_INTERPOLATION);\n      return;\n    }\n\n    // regular string start (always full string if single quotes)\n    matcher = REGEX_STRING_NON_INTERPOLATED_PART.matcher(this.source);\n    if (matcher.lookingAt() && matcher.end() > 0) {\n      String token = this.source.substring(matcher.end());\n      this.source.advance(matcher.end());\n      this.pushToken(Token.Type.STRING, token);\n      return;\n    }\n\n    // end of string (which may have contained interpolation)\n    matcher = REGEX_DOUBLEQUOTE.matcher(this.source);\n    if (matcher.lookingAt()) {\n      String expected = this.brackets.pop().getLeft();\n\n      if (this.source.charAt(0) != '\"') {\n        throw new ParserException(null, String.format(\"Unclosed \\\"%s\\\"\", expected),\n            this.source.getLineNumber(), this.source.getFilename());\n      }\n\n      this.popState();\n      this.source.advance(matcher.end());\n    }\n  }\n\n  /**\n   * The DATA state assumes that we are current NOT in between any pair of meaningful delimiters. We\n   * are currently looking for the next \"open\" or \"start\" delimiter, ex. the opening comment\n   * delimiter, or the opening variable delimiter.\n   */\n  private void tokenizeData() {\n    this.logger.trace(\"Tokenizing Data\");\n    // find the next start delimiter\n    Matcher matcher = this.syntax.getRegexStartDelimiters().matcher(this.source);\n    boolean match = matcher.find();\n\n    String text;\n    String startDelimiter = null;\n\n    // if we didn't find another start delimiter, the text\n    // token goes all the way to the end of the template.\n    if (!match) {\n      this.logger\n          .trace(\"Advancing to the end of the template because no start delimiter was found\");\n      text = this.source.toString();\n      this.source.advance(this.source.length());\n    } else {\n      text = this.source.substring(matcher.start());\n      startDelimiter = this.source.substring(matcher.start(), matcher.end());\n      this.logger.trace(\"Start Deliminter Token string: {}\", startDelimiter);\n      // advance to after the start delimiter\n      this.source.advance(matcher.end());\n    }\n\n    // trim leading whitespace from this text if we previously\n    // encountered the appropriate whitespace trim character\n    if (this.trimLeadingWhitespaceFromNextData) {\n      this.logger.trace(\"Left Trimming text\");\n      text = StringUtils.ltrim(text);\n      this.trimLeadingWhitespaceFromNextData = false;\n    }\n    Token textToken = this.pushToken(Type.TEXT, text);\n\n    if (match) {\n      this.checkForLeadingWhitespaceTrim(textToken);\n      if (this.syntax.getCommentOpenDelimiter().equals(startDelimiter)) {\n        // we don't actually push any tokens for comments\n        this.lexerStateStack.push(State.COMMENT);\n      } else if (this.syntax.getPrintOpenDelimiter().equals(startDelimiter)) {\n        this.pushToken(Token.Type.PRINT_START);\n        this.lexerStateStack.push(State.PRINT);\n      } else if ((this.syntax.getExecuteOpenDelimiter().equals(startDelimiter))) {\n        // check for verbatim tag\n        Matcher verbatimStartMatcher = this.syntax.getRegexVerbatimStart().matcher(this.source);\n        if (verbatimStartMatcher.lookingAt()) {\n          this.lexVerbatimData(verbatimStartMatcher);\n          this.lexerStateStack.push(State.DATA);\n        } else {\n          this.pushToken(Token.Type.EXECUTE_START);\n          this.lexerStateStack.push(State.EXECUTE);\n        }\n      }\n    }\n  }\n\n  /**\n   * Tokenizes between execute delimiters.\n   */\n  private void tokenizeBetweenExecuteDelimiters() {\n    this.logger.trace(\"Tokenize between execute delimiters\");\n    // check for the trailing whitespace trim character\n    this.checkForTrailingWhitespaceTrim();\n\n    Matcher matcher = this.syntax.getRegexExecuteClose().matcher(this.source);\n\n    // check if we are at the execute closing delimiter\n    if (this.brackets.isEmpty() && matcher.lookingAt()) {\n      this.pushToken(Token.Type.EXECUTE_END, this.syntax.getExecuteCloseDelimiter());\n      this.source.advance(matcher.end());\n      this.popState();\n    } else {\n      this.tokenizeExpression();\n    }\n  }\n\n  /**\n   * Tokenizes between print delimiters.\n   */\n  private void tokenizeBetweenPrintDelimiters() {\n\n    // check for the trailing whitespace trim character\n    this.checkForTrailingWhitespaceTrim();\n\n    Matcher matcher = this.syntax.getRegexPrintClose().matcher(this.source);\n\n    // check if we are at the print closing delimiter\n    if (this.brackets.isEmpty() && matcher.lookingAt()) {\n      this.pushToken(Token.Type.PRINT_END, this.syntax.getPrintCloseDelimiter());\n      this.source.advance(matcher.end());\n      this.popState();\n    } else {\n      this.tokenizeExpression();\n    }\n  }\n\n  /**\n   * Tokenizes between comment delimiters.\n   * <p>\n   * Simply find the closing delimiter for the comment and move the cursor to that point.\n   */\n  private void tokenizeComment() {\n\n    // all we need to do is find the end of the comment.\n    Matcher matcher = this.syntax.getRegexCommentClose().matcher(this.source);\n\n    boolean match = matcher.find(0);\n    if (!match) {\n      throw new ParserException(null, \"Unclosed comment.\", this.source.getLineNumber(),\n          this.source.getFilename());\n    }\n\n    /*\n     * check if the commented ended with the whitespace trim character by reversing the comment and\n     * performing a regular forward regex search.\n     */\n    String comment = this.source.substring(matcher.start());\n    String reversedComment = new StringBuilder(comment).reverse().toString();\n    Matcher whitespaceTrimMatcher =\n        this.syntax.getRegexLeadingWhitespaceTrim().matcher(reversedComment);\n    if (whitespaceTrimMatcher.lookingAt()) {\n      this.trimLeadingWhitespaceFromNextData = true;\n    }\n\n    // move cursor to end of comment (and closing delimiter)\n    this.source.advance(matcher.end());\n    this.popState();\n  }\n\n  /**\n   * Tokenizing an expression which can be found within both execute and print regions.\n   */\n  private void tokenizeExpression() {\n    this.logger.trace(\"Tokenizing Expression\");\n    String token;\n    this.source.advanceThroughWhitespace();\n    /*\n     * Matcher matcher = REGEX_WHITESPACE.matcher(source); if (matcher.lookingAt()) {\n     * source.advance(matcher.end()); }\n     */\n\n    // operators\n    Matcher matcher = this.regexOperators.matcher(this.source);\n    if (matcher.lookingAt()) {\n      token = this.source.substring(matcher.end());\n      this.pushToken(Token.Type.OPERATOR, token);\n      this.source.advance(matcher.end());\n      return;\n    }\n\n    // names\n    matcher = REGEX_IDENTIFIER.matcher(this.source);\n    if (matcher.lookingAt()) {\n      token = this.source.substring(matcher.end());\n      this.pushToken(Token.Type.NAME, token);\n      this.source.advance(matcher.end());\n      return;\n    }\n\n    // long\n    matcher = REGEX_LONG.matcher(this.source);\n    if (matcher.lookingAt()) {\n      token = this.source.substring(matcher.end() - 1);\n      this.pushToken(Token.Type.LONG, token);\n      this.source.advance(matcher.end());\n      return;\n    }\n\n    // numbers\n    matcher = REGEX_NUMBER.matcher(this.source);\n    if (matcher.lookingAt()) {\n      token = this.source.substring(matcher.end());\n      this.pushToken(Token.Type.NUMBER, token);\n      this.source.advance(matcher.end());\n      return;\n    }\n\n    // punctuation\n    if (PUNCTUATION.indexOf(this.source.charAt(0)) >= 0) {\n      String character = String.valueOf(this.source.charAt(0));\n\n      // opening bracket\n      if (\"([{\".contains(character)) {\n        this.brackets.push(new Pair<>(character, this.source.getLineNumber()));\n      }\n\n      // closing bracket\n      else if (\")]}\".contains(character)) {\n        if (this.brackets.isEmpty()) {\n          throw new ParserException(null, \"Unexpected \\\"\" + character + \"\\\"\",\n              this.source.getLineNumber(), this.source.getFilename());\n        } else {\n          HashMap<String, String> validPairs = new HashMap<>();\n          validPairs.put(\"(\", \")\");\n          validPairs.put(\"[\", \"]\");\n          validPairs.put(\"{\", \"}\");\n          String lastBracket = this.brackets.pop().getLeft();\n          String expected = validPairs.get(lastBracket);\n          if (!expected.equals(character)) {\n            throw new ParserException(null, \"Unclosed \\\"\" + expected + \"\\\"\",\n                this.source.getLineNumber(), this.source.getFilename());\n          }\n        }\n      }\n\n      this.pushToken(Token.Type.PUNCTUATION, character);\n      this.source.advance(1);\n      return;\n    }\n\n    // Plain (non-interpolated) string\n    matcher = REGEX_STRING_PLAIN.matcher(this.source);\n    if (matcher.lookingAt()) {\n      token = this.source.substring(matcher.end());\n      this.source.advance(matcher.end());\n      token = this.unquoteAndUnescape(token);\n      this.pushToken(Token.Type.STRING, token);\n      return;\n    }\n\n    // Interpolated strings\n    matcher = REGEX_DOUBLEQUOTE.matcher(this.source);\n    if (matcher.lookingAt()) {\n      this.brackets.push(new Pair<>(\"\\\"\", this.source.getLineNumber()));\n      this.lexerStateStack.push(State.STRING);\n      this.source.advance(matcher.end());\n      return;\n    }\n\n    // we should have found something and returned by this point\n    throw new ParserException(null,\n        String.format(\"Unexpected character [%s]\", this.source.charAt(0)),\n        this.source.getLineNumber(), this.source.getFilename());\n  }\n\n  /**\n   * This method assumes the provided {@code str} starts with a single or double quote. It removes\n   * the wrapping quotes, and un-escapes any quotes within the string.\n   */\n  private String unquoteAndUnescape(String str) {\n    char quotationType = str.charAt(0);\n\n    // remove first and last quotation marks\n    str = str.substring(1, str.length() - 1);\n\n    // remove backslashes used to escape inner quotation marks\n    if (quotationType == '\\'') {\n      str = str.replaceAll(\"\\\\\\\\(')\", \"$1\");\n    } else if (quotationType == '\"') {\n      str = str.replaceAll(\"\\\\\\\\(\\\")\", \"$1\");\n    }\n    return str;\n  }\n\n  private void checkForLeadingWhitespaceTrim(Token leadingToken) {\n\n    Matcher whitespaceTrimMatcher =\n        this.syntax.getRegexLeadingWhitespaceTrim().matcher(this.source);\n\n    if (whitespaceTrimMatcher.lookingAt()) {\n      this.logger.trace(\"Found Leading Whitespace Trim Character\");\n      if (leadingToken != null) {\n        this.logger.trace(\"Right trimming leading token: {}\", leadingToken);\n        leadingToken.setValue(StringUtils.rtrim(leadingToken.getValue()));\n      }\n      this.source.advance(whitespaceTrimMatcher.end());\n    }\n\n  }\n\n  private void checkForTrailingWhitespaceTrim() {\n    Matcher whitespaceTrimMatcher =\n        this.syntax.getRegexTrailingWhitespaceTrim().matcher(this.source);\n\n    if (whitespaceTrimMatcher.lookingAt()) {\n      this.trimLeadingWhitespaceFromNextData = true;\n    }\n  }\n\n  /**\n   * Implementation of the \"verbatim\" tag\n   */\n  private void lexVerbatimData(Matcher verbatimStartMatcher) {\n\n    // move cursor past the opening verbatim tag\n    this.source.advance(verbatimStartMatcher.end());\n\n    // look for the \"endverbatim\" tag and storing everything between\n    // now and then into a TEXT node\n    Matcher verbatimEndMatcher = this.syntax.getRegexVerbatimEnd().matcher(this.source);\n\n    // check for EOF\n    if (!verbatimEndMatcher.find()) {\n      throw new ParserException(null, \"Unclosed verbatim tag.\", this.source.getLineNumber(),\n          this.source.getFilename());\n    }\n    String verbatimText = this.source.substring(verbatimEndMatcher.start());\n\n    // check if the verbatim start tag has a trailing whitespace trim\n    if (verbatimStartMatcher.group(0) != null) {\n      verbatimText = StringUtils.ltrim(verbatimText);\n    }\n\n    // check if the verbatim end tag had a leading whitespace trim\n    if (verbatimEndMatcher.group(1) != null) {\n      verbatimText = StringUtils.rtrim(verbatimText);\n    }\n\n    // check if the verbatim end tag had a trailing whitespace trim\n    if (verbatimEndMatcher.group(2) != null) {\n      this.trimLeadingWhitespaceFromNextData = true;\n    }\n\n    // move cursor past the verbatim text and end delimiter\n    this.source.advance(verbatimEndMatcher.end());\n\n    this.pushToken(Type.TEXT, verbatimText);\n  }\n\n  /**\n   * Create a Token with a Token Type but without no value onto the list of tokens that we are\n   * maintaining.\n   *\n   * @param type The type of Token we are creating\n   */\n  private Token pushToken(Token.Type type) {\n    Token token = this.pushToken(type, null);\n    return token;\n  }\n\n  /**\n   * Create a Token of a certain type and value and push it into the list of tokens that we are\n   * maintaining. `\n   *\n   * @param type The type of token we are creating\n   * @param value The value of the new token\n   */\n  private Token pushToken(Token.Type type, String value) {\n    // ignore empty text tokens\n    if (type.equals(Token.Type.TEXT) && (value == null || \"\".equals(value))) {\n      this.logger.trace(\"Skipping empty text token\");\n      return null;\n    }\n\n    Token token = new Token(type, value, this.source.getLineNumber());\n    this.tokens.add(token);\n    this.logger.trace(\"Pushing Token: {}\", token);\n\n    return token;\n  }\n\n  /**\n   * Pop state from the stack\n   */\n  private void popState() {\n    this.lexerStateStack.pop();\n  }\n\n  /**\n   * Retrieves the operators (both unary and binary) from the PebbleEngine and then dynamically\n   * creates one giant regular expression to detect for the existence of one of these operators.\n   */\n  private void buildOperatorRegex() {\n\n    List<String> operators = new ArrayList<>();\n\n    for (UnaryOperator operator : this.unaryOperators) {\n      operators.add(operator.getSymbol());\n    }\n\n    for (BinaryOperator operator : this.binaryOperators) {\n      operators.add(operator.getSymbol());\n    }\n\n    /*\n     * Since java's matcher doesn't conform with the posix standard of matching the longest\n     * alternative (it matches the first alternative), we must first sort all of the operators by\n     * length before creating the regex. This is to help match \"is not\" over \"is\".\n     */\n    operators.sort(StringLengthComparator.INSTANCE);\n\n    StringBuilder regex = new StringBuilder(\"^\");\n\n    boolean isFirst = true;\n    for (String operator : operators) {\n      if (isFirst) {\n        isFirst = false;\n      } else {\n        regex.append(\"|\");\n      }\n      regex.append(Pattern.quote(operator));\n\n      /*\n       * If the operator ends in an alpha character we use a negative lookahead assertion to make\n       * sure the next character in the stream is NOT an alpha character. This ensures user can type\n       * \"organization\" without the \"or\" being parsed as an operator.\n       */\n      char nextChar = operator.charAt(operator.length() - 1);\n      if (Character.isLetter(nextChar) || Character.getType(nextChar) == Character.LETTER_NUMBER) {\n        regex.append(\"(?![a-zA-Z0-9_])\");\n      }\n    }\n\n    this.regexOperators = Pattern.compile(regex.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/lexer/Syntax.java",
    "content": "package io.pebbletemplates.pebble.lexer;\n\nimport java.util.regex.Pattern;\n\n/**\n * The syntax describes the different syntax parts of the Pebble language.\n *\n * <p>\n * This object is immutable after the creation. This is to make sure the syntax cannot be changed\n * during the execution.\n */\npublic final class Syntax {\n\n  private final String delimiterCommentOpen;\n\n  private final String delimiterCommentClose;\n\n  private final String delimiterExecuteOpen;\n\n  private final String delimiterExecuteClose;\n\n  private final String delimiterPrintOpen;\n\n  private final String delimiterPrintClose;\n\n  private final String delimiterInterpolationOpen;\n\n  private final String delimiterInterpolationClose;\n\n  private final String whitespaceTrim;\n\n  /**\n   * The regular expressions used to find the different delimiters\n   */\n  private final Pattern regexPrintClose;\n\n  private final Pattern regexExecuteClose;\n\n  private final Pattern regexCommentClose;\n\n  private final Pattern regexStartDelimiters;\n\n  private final Pattern regexLeadingWhitespaceTrim;\n\n  private final Pattern regexTrailingWhitespaceTrim;\n\n  private final Pattern regexInterpolationOpen;\n  private final Pattern regexInterpolationClose;\n\n  /**\n   * Regular expressions used to find \"verbatim\" and \"endverbatim\" tags.\n   */\n  private final Pattern regexVerbatimStart;\n\n  private final Pattern regexVerbatimEnd;\n\n  private static final String POSSIBLE_NEW_LINE = \"(\\r\\n|\\n\\r|\\r|\\n|\\u0085|\\u2028|\\u2029)?\";\n\n  public Syntax(final String delimiterCommentOpen, final String delimiterCommentClose,\n      final String delimiterExecuteOpen, final String delimiterExecuteClose,\n      final String delimiterPrintOpen,\n      final String delimiterPrintClose, final String delimiterInterpolationOpen,\n      final String delimiterInterpolationClose, final String whitespaceTrim,\n      final boolean enableNewLineTrimming) {\n    this.delimiterCommentClose = delimiterCommentClose;\n    this.delimiterCommentOpen = delimiterCommentOpen;\n    this.delimiterExecuteOpen = delimiterExecuteOpen;\n    this.delimiterExecuteClose = delimiterExecuteClose;\n    this.delimiterPrintOpen = delimiterPrintOpen;\n    this.delimiterPrintClose = delimiterPrintClose;\n    this.whitespaceTrim = whitespaceTrim;\n    this.delimiterInterpolationClose = delimiterInterpolationClose;\n    this.delimiterInterpolationOpen = delimiterInterpolationOpen;\n\n    // Do we trim the newline following a tag?\n    String newlineRegexSuffix = enableNewLineTrimming ? POSSIBLE_NEW_LINE : \"\";\n\n    // regexes used to find the individual delimiters\n    this.regexPrintClose = Pattern.compile(\"^\\\\s*\" + Pattern.quote(whitespaceTrim) + \"?\"\n        + Pattern.quote(delimiterPrintClose) + newlineRegexSuffix);\n\n    this.regexExecuteClose = Pattern.compile(\"^\\\\s*\" + Pattern.quote(whitespaceTrim) + \"?\"\n        + Pattern.quote(delimiterExecuteClose) + newlineRegexSuffix);\n    this.regexCommentClose = Pattern\n        .compile(Pattern.quote(delimiterCommentClose) + newlineRegexSuffix);\n\n    // combination regex used to find the next START delimiter of any kind\n    this.regexStartDelimiters = Pattern.compile(Pattern.quote(delimiterPrintOpen) + \"|\"\n        + Pattern.quote(delimiterExecuteOpen) + \"|\" + Pattern.quote(delimiterCommentOpen));\n\n    // regex to find the verbatim tag\n    this.regexVerbatimStart = Pattern\n        .compile(\"^\\\\s*verbatim\\\\s*(\" + Pattern.quote(whitespaceTrim) + \")?\"\n            + Pattern.quote(delimiterExecuteClose) + newlineRegexSuffix);\n    this.regexVerbatimEnd = Pattern.compile(Pattern.quote(delimiterExecuteOpen) + \"(\"\n        + Pattern.quote(whitespaceTrim) + \")?\" + \"\\\\s*endverbatim\\\\s*(\" + Pattern\n        .quote(whitespaceTrim) + \")?\"\n        + Pattern.quote(delimiterExecuteClose) + newlineRegexSuffix);\n\n    // regex for the whitespace trim character\n    this.regexLeadingWhitespaceTrim = Pattern.compile(Pattern.quote(whitespaceTrim) + \"\\\\s+\");\n    this.regexTrailingWhitespaceTrim = Pattern.compile(\"^\\\\s*\" + Pattern.quote(whitespaceTrim) + \"(\"\n        + Pattern.quote(delimiterPrintClose) + \"|\" + Pattern.quote(delimiterExecuteClose) + \"|\"\n        + Pattern.quote(delimiterCommentClose) + \")\");\n\n    this.regexInterpolationOpen = Pattern.compile(\"^\" + Pattern.quote(delimiterInterpolationOpen));\n    this.regexInterpolationClose = Pattern\n        .compile(\"^\\\\s*\" + Pattern.quote(delimiterInterpolationClose));\n\n  }\n\n  /**\n   * @return the commentOpenDelimiter\n   */\n  public String getCommentOpenDelimiter() {\n    return delimiterCommentOpen;\n  }\n\n  /**\n   * @return the commentCloseDelimiter\n   */\n  public String getCommentCloseDelimiter() {\n    return delimiterCommentClose;\n  }\n\n  /**\n   * @return the executeOpenDelimiter\n   */\n  public String getExecuteOpenDelimiter() {\n    return delimiterExecuteOpen;\n  }\n\n  /**\n   * @return the executeCloseDelimiter\n   */\n  public String getExecuteCloseDelimiter() {\n    return delimiterExecuteClose;\n  }\n\n  /**\n   * @return the printOpenDelimiter\n   */\n  public String getPrintOpenDelimiter() {\n    return delimiterPrintOpen;\n  }\n\n  /**\n   * @return the printCloseDelimiter\n   */\n  public String getPrintCloseDelimiter() {\n    return delimiterPrintClose;\n  }\n\n  public String getInterpolationOpenDelimiter() {\n    return delimiterInterpolationOpen;\n  }\n\n  public String getInterpolationCloseDelimiter() {\n    return delimiterInterpolationClose;\n  }\n\n  public String getWhitespaceTrim() {\n    return whitespaceTrim;\n  }\n\n  Pattern getRegexPrintClose() {\n    return regexPrintClose;\n  }\n\n  Pattern getRegexExecuteClose() {\n    return regexExecuteClose;\n  }\n\n  Pattern getRegexCommentClose() {\n    return regexCommentClose;\n  }\n\n  Pattern getRegexStartDelimiters() {\n    return regexStartDelimiters;\n  }\n\n  Pattern getRegexLeadingWhitespaceTrim() {\n    return regexLeadingWhitespaceTrim;\n  }\n\n  Pattern getRegexTrailingWhitespaceTrim() {\n    return regexTrailingWhitespaceTrim;\n  }\n\n  Pattern getRegexVerbatimEnd() {\n    return regexVerbatimEnd;\n  }\n\n  Pattern getRegexVerbatimStart() {\n    return regexVerbatimStart;\n  }\n\n  Pattern getRegexInterpolationOpen() {\n    return regexInterpolationOpen;\n  }\n\n  Pattern getRegexInterpolationClose() {\n    return regexInterpolationClose;\n  }\n\n  /**\n   * Helper class to create new instances of {@link Syntax}.\n   */\n  public static class Builder {\n\n    private String delimiterCommentOpen = \"{#\";\n\n    private String delimiterCommentClose = \"#}\";\n\n    private String delimiterExecuteOpen = \"{%\";\n\n    private String delimiterExecuteClose = \"%}\";\n\n    private String delimiterPrintOpen = \"{{\";\n\n    private String delimiterPrintClose = \"}}\";\n\n    private String delimiterInterpolationOpen = \"#{\";\n\n    private String delimiterInterpolationClose = \"}\";\n\n    private String whitespaceTrim = \"-\";\n\n    private boolean enableNewLineTrimming = true;\n\n    /**\n     * @return the commentOpenDelimiter\n     */\n    public String getCommentOpenDelimiter() {\n      return delimiterCommentOpen;\n    }\n\n    /**\n     * @param commentOpenDelimiter the commentOpenDelimiter to set\n     * @return This builder object\n     */\n    public Builder setCommentOpenDelimiter(String commentOpenDelimiter) {\n      this.delimiterCommentOpen = commentOpenDelimiter;\n      return this;\n    }\n\n    /**\n     * @return the commentCloseDelimiter\n     */\n    public String getCommentCloseDelimiter() {\n      return delimiterCommentClose;\n    }\n\n    /**\n     * @param commentCloseDelimiter the commentCloseDelimiter to set\n     * @return This builder object\n     */\n    public Builder setCommentCloseDelimiter(String commentCloseDelimiter) {\n      this.delimiterCommentClose = commentCloseDelimiter;\n      return this;\n    }\n\n    /**\n     * @return the executeOpenDelimiter\n     */\n    public String getExecuteOpenDelimiter() {\n      return delimiterExecuteOpen;\n    }\n\n    /**\n     * @param executeOpenDelimiter the executeOpenDelimiter to set\n     * @return This builder object\n     */\n    public Builder setExecuteOpenDelimiter(String executeOpenDelimiter) {\n      this.delimiterExecuteOpen = executeOpenDelimiter;\n      return this;\n    }\n\n    /**\n     * @return the executeCloseDelimiter\n     */\n    public String getExecuteCloseDelimiter() {\n      return delimiterExecuteClose;\n    }\n\n    /**\n     * @param executeCloseDelimiter the executeCloseDelimiter to set\n     * @return This builder object\n     */\n    public Builder setExecuteCloseDelimiter(String executeCloseDelimiter) {\n      this.delimiterExecuteClose = executeCloseDelimiter;\n      return this;\n    }\n\n    /**\n     * @return the printOpenDelimiter\n     */\n    public String getPrintOpenDelimiter() {\n      return delimiterPrintOpen;\n    }\n\n    /**\n     * @param printOpenDelimiter the printOpenDelimiter to set\n     * @return This builder object\n     */\n    public Builder setPrintOpenDelimiter(String printOpenDelimiter) {\n      this.delimiterPrintOpen = printOpenDelimiter;\n      return this;\n    }\n\n    /**\n     * @return the printCloseDelimiter\n     */\n    public String getPrintCloseDelimiter() {\n      return delimiterPrintClose;\n    }\n\n    /**\n     * @param printCloseDelimiter the printCloseDelimiter to set\n     * @return This builder object\n     */\n    public Builder setPrintCloseDelimiter(String printCloseDelimiter) {\n      this.delimiterPrintClose = printCloseDelimiter;\n      return this;\n    }\n\n    public String getWhitespaceTrim() {\n      return whitespaceTrim;\n    }\n\n    public Builder setWhitespaceTrim(String whitespaceTrim) {\n      this.whitespaceTrim = whitespaceTrim;\n      return this;\n    }\n\n    public String getInterpolationOpenDelimiter() {\n      return delimiterInterpolationOpen;\n    }\n\n    public void setInterpolationOpenDelimiter(String delimiterInterpolationOpen) {\n      this.delimiterInterpolationOpen = delimiterInterpolationOpen;\n    }\n\n    public String getInterpolationCloseDelimiter() {\n      return delimiterInterpolationClose;\n    }\n\n    public void setInterpolationCloseDelimiter(String delimiterInterpolationClose) {\n      this.delimiterInterpolationClose = delimiterInterpolationClose;\n    }\n\n    public boolean isEnableNewLineTrimming() {\n      return enableNewLineTrimming;\n    }\n\n    public Builder setEnableNewLineTrimming(boolean enableNewLineTrimming) {\n      this.enableNewLineTrimming = enableNewLineTrimming;\n      return this;\n    }\n\n    public Syntax build() {\n      return new Syntax(delimiterCommentOpen, delimiterCommentClose, delimiterExecuteOpen,\n          delimiterExecuteClose,\n          delimiterPrintOpen, delimiterPrintClose, delimiterInterpolationOpen,\n          delimiterInterpolationClose,\n          whitespaceTrim, enableNewLineTrimming);\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/lexer/TemplateSource.java",
    "content": "package io.pebbletemplates.pebble.lexer;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.util.Arrays;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * An implementation of CharSequence that is tuned to be used specifically by {@link LexerImpl}. It\n * is possible to advance through the sequence without allocating a copy and it is possible to\n * perform regex matches from the logical beginning of the remaining un-tokenized source. This class\n * will also standardize newline characters from different architectures.\n *\n * @author mbosecke\n */\npublic class TemplateSource implements CharSequence {\n\n  private final Logger logger = LoggerFactory.getLogger(TemplateSource.class);\n\n  /**\n   * The characters found within the template.\n   */\n  private char source[];\n\n  /**\n   * Number of characters stored in source array remaining to be tokenized\n   */\n  private int size = 0;\n\n  /**\n   * Default capacity\n   */\n  private static final int DEFAULT_CAPACITY = 1024;\n\n  /**\n   * An index of the first character for the remaining un-tokenized source.\n   */\n  private int offset = 0;\n\n  /**\n   * Tracking the line number that we are currently tokenizing.\n   */\n  private int lineNumber = 1;\n\n  /**\n   * Filename of the template\n   */\n  private final String filename;\n\n  /**\n   * Constructor\n   *\n   * @param reader Reader provided by the Loader\n   * @param filename Filename of the template\n   * @throws IOException Exceptions thrown from the reader\n   */\n  public TemplateSource(Reader reader, String filename) throws IOException {\n    this.filename = filename;\n    this.source = new char[DEFAULT_CAPACITY];\n    copyReaderIntoCharArray(reader);\n  }\n\n  /**\n   * Read the contents of the template into the internal char[].\n   */\n  private void copyReaderIntoCharArray(Reader reader) throws IOException {\n    char[] buffer = new char[1024 * 4];\n    int amountJustRead;\n    while ((amountJustRead = reader.read(buffer)) != -1) {\n\n      ensureCapacity(size + amountJustRead);\n      append(buffer, amountJustRead);\n    }\n    reader.close();\n  }\n\n  /**\n   * Append characters to the internal array.\n   */\n  private void append(char[] characters, int amount) {\n    System.arraycopy(characters, 0, source, size, amount);\n    size += amount;\n  }\n\n  /**\n   * Ensure that the internal array has a minimum capacity.\n   */\n  private void ensureCapacity(int minCapacity) {\n    if (source.length - minCapacity < 0) {\n      grow(minCapacity);\n    }\n  }\n\n  /**\n   * Grow the internal array to at least the desired minimum capacity.\n   */\n  private void grow(int minCapacity) {\n    int oldCapacity = source.length;\n\n    /*\n     * double the capacity of the array and if that's not enough, just use\n     * the minCapacity\n     */\n    int newCapacity = Math.max(oldCapacity << 1, minCapacity);\n\n    this.source = Arrays.copyOf(source, newCapacity);\n  }\n\n  /**\n   * Moves the start index a certain amount. While traversing this amount we will count how many\n   * newlines have been encountered.\n   *\n   * @param amount Amount of characters to advance by\n   */\n  public void advance(int amount) {\n\tlogger.trace(\"Advancing amount: {}\", amount);\n    int index = 0;\n    while (index < amount) {\n      int sizeOfNewline = advanceThroughNewline(index);\n\n      if (sizeOfNewline > 0) {\n        index += sizeOfNewline;\n      } else {\n        index++;\n      }\n    }\n\n    this.size -= amount;\n    this.offset += amount;\n  }\n\n  public void advanceThroughWhitespace() {\n    int index = 0;\n    while (Character.isWhitespace(this.charAt(index))) {\n      int sizeOfNewline = advanceThroughNewline(index);\n      if (sizeOfNewline > 0) {\n        index += sizeOfNewline;\n      } else {\n        index++;\n      }\n    }\n    logger.trace(\"Advanced through {} characters of whitespace.\", index);\n    this.size -= index;\n    this.offset += index;\n  }\n\n  /**\n   * Advances through possible newline character and returns how many characters were used to\n   * represent the newline (windows uses two characters to represent one newline).\n   *\n   * @param index The index of the potential newline character\n   */\n  private int advanceThroughNewline(int index) {\n    char character = this.charAt(index);\n    int numOfCharacters = 0;\n\n    // windows newline\n    if ('\\r' == character && '\\n' == this.charAt(index + 1)) {\n\n      this.lineNumber++;\n      numOfCharacters = 2;\n\n      // various other newline characters\n    } else if ('\\n' == character || '\\r' == character || '\\u0085' == character\n        || '\\u2028' == character\n        || '\\u2029' == character) {\n\n      this.lineNumber++;\n      numOfCharacters = 1;\n    }\n    return numOfCharacters;\n  }\n\n  public String substring(int start, int end) {\n    return new String(Arrays.copyOfRange(source, this.offset + start, this.offset + end));\n  }\n\n  public String substring(int end) {\n    return new String(Arrays.copyOfRange(source, offset, offset + end));\n  }\n\n  @Override\n  public int length() {\n    return size;\n  }\n\n  @Override\n  public char charAt(int index) {\n    return source[offset + index];\n  }\n\n  @Override\n  public CharSequence subSequence(int start, int end) {\n    return new String(Arrays.copyOfRange(source, this.offset + start, this.offset + end));\n  }\n\n  public String toString() {\n    return new String(Arrays.copyOfRange(source, offset, offset + size));\n  }\n\n  public int getLineNumber() {\n    return lineNumber;\n  }\n\n  public String getFilename() {\n    return filename;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/lexer/Token.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE file that was distributed\n * with this source code.\n */\npackage io.pebbletemplates.pebble.lexer;\n\nimport java.util.Arrays;\n\npublic class Token {\n\n  private String value;\n\n  private Type type;\n\n  private int lineNumber;\n\n  public enum Type {\n    EOF, TEXT, EXECUTE_START, EXECUTE_END, PRINT_START, PRINT_END, NAME, NUMBER, LONG, STRING, OPERATOR, PUNCTUATION, STRING_INTERPOLATION_START, STRING_INTERPOLATION_END\n  }\n\n  public Token(Type type, String value, int lineNumber) {\n    this.type = type;\n    this.value = value;\n    this.lineNumber = lineNumber;\n  }\n\n  public boolean test(Type type) {\n    return this.test(type, new String[0]);\n  }\n\n  public boolean test(Type type, String... values) {\n    boolean test = true;\n    if (values.length > 0) {\n      test = Arrays.asList(values).contains(this.value);\n    }\n    return test && this.type.equals(type);\n  }\n\n  public String getValue() {\n    return this.value;\n  }\n\n  public void setValue(String value) {\n    this.value = value;\n  }\n\n  public Type getType() {\n    return this.type;\n  }\n\n  public void setType(Type type) {\n    this.type = type;\n  }\n\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public void setLineNumber(int lineNumber) {\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public String toString() {\n    return \"Token [value=\" + value + \", type=\" + type + \", lineNumber=\" + lineNumber + \"]\";\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/lexer/TokenStream.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.lexer;\n\nimport io.pebbletemplates.pebble.error.ParserException;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\n\npublic class TokenStream {\n\n  private ArrayList<Token> tokens = new ArrayList<>();\n\n  private int current;\n\n  private String filename;\n\n  /**\n   * Constructor for a Token Stream\n   *\n   * @param tokens A collection of tokens\n   * @param name The filename of the template that these tokens came from\n   */\n  public TokenStream(Collection<Token> tokens, String name) {\n    this.tokens.addAll(tokens);\n    this.current = 0;\n    this.filename = name;\n  }\n\n  /**\n   * Consumes and returns the next token in the stream.\n   *\n   * @return The next token\n   */\n  public Token next() {\n    return this.tokens.get(++this.current);\n  }\n\n  /**\n   * Checks the current token to see if it matches the provided type. If it doesn't match this will\n   * throw a SyntaxException. This will consume a token.\n   *\n   * @param type The type of token that we expect\n   * @return Token The current token\n   */\n  public Token expect(Token.Type type) {\n    return this.expect(type, null);\n  }\n\n  /**\n   * Checks the current token to see if it matches the provided type. If it doesn't match this will\n   * throw a SyntaxException. This will consume a token.\n   *\n   * @param type The type of token that we expect\n   * @param value The expected value of the token\n   * @return Token The current token\n   */\n  public Token expect(Token.Type type, String value) {\n    Token token = this.tokens.get(this.current);\n\n    boolean success = value == null ? token.test(type) : token.test(type, value);\n\n    if (!success) {\n      String message = String\n          .format(\"Unexpected token of value \\\"%s\\\" and type %s, expected token of type %s\",\n              token.getValue(), token.getType().toString(), type);\n      throw new ParserException(null, message, token.getLineNumber(), this.filename);\n    }\n    this.next();\n    return token;\n  }\n\n  /**\n   * Returns the next token in the stream without consuming it.\n   *\n   * @return The next token\n   */\n  public Token peek() {\n    return this.peek(1);\n  }\n\n  /**\n   * Returns a future token in the stream without consuming any.\n   *\n   * @param number How many tokens to lookahead\n   * @return The token we are peeking at\n   */\n  public Token peek(int number) {\n    return this.tokens.get(this.current + number);\n  }\n\n  public boolean isEOF() {\n    return this.tokens.get(this.current).getType().equals(Token.Type.EOF);\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"Current: %s. All: %s\", this.current(), this.tokens);\n  }\n\n  /**\n   * Looks at the current token. Does not consume the token.\n   *\n   * @return Token The current token\n   */\n  public Token current() {\n    return this.tokens.get(this.current);\n  }\n\n  public String getFilename() {\n    return this.filename;\n  }\n\n  /**\n   * used for testing purposes\n   *\n   * @return List of tokens\n   */\n  public ArrayList<Token> getTokens() {\n    return this.tokens;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/AbstractServletLoader.java",
    "content": "package io.pebbletemplates.pebble.loader;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\nimport io.pebbletemplates.pebble.utils.PathUtils;\n\nimport java.io.BufferedReader;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.io.UnsupportedEncodingException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Abstract base class for loaders which user the servlet context to load templates.\n *\n * @author mbosecke\n * @author chkal\n */\npublic abstract class AbstractServletLoader implements Loader<String> {\n\n  private static final Logger logger = LoggerFactory.getLogger(AbstractServletLoader.class);\n\n  private String prefix;\n\n  private String suffix;\n\n  private String charset = \"UTF-8\";\n\n  private char expectedSeparator = '/';\n\n  protected abstract InputStream getResourceAsStream(String location);\n\n  protected abstract URL getResource(String location) throws MalformedURLException;\n\n  @Override\n  public Reader getReader(String templateName) {\n\n    InputStreamReader isr = null;\n    Reader reader = null;\n\n    InputStream is = null;\n    String location = this.getLocation(templateName);\n\n    logger.debug(\"Looking for template in {}.\", location);\n\n    is = getResourceAsStream(location);\n\n    if (is == null) {\n      throw new LoaderException(null, \"Could not find template \\\"\" + location + \"\\\"\");\n    }\n\n    try {\n      isr = new InputStreamReader(is, this.charset);\n      reader = new BufferedReader(isr);\n    } catch (UnsupportedEncodingException e) {\n    }\n\n    return reader;\n  }\n\n  private String getLocation(String templateName) {\n    // Add the prefix and make sure that it ends with a separator character\n    StringBuilder path = new StringBuilder(128);\n    if (this.getPrefix() != null) {\n\n      path.append(this.getPrefix());\n\n      // we do NOT use OS dependent separators here; getResourceAsStream\n      // explicitly requires forward slashes.\n      if (!this.getPrefix().endsWith(Character.toString(this.expectedSeparator))) {\n        path.append(this.expectedSeparator);\n      }\n    }\n    path.append(templateName);\n    if (this.getSuffix() != null) {\n      path.append(this.getSuffix());\n    }\n    return path.toString();\n  }\n\n  public String getSuffix() {\n    return this.suffix;\n  }\n\n  @Override\n  public void setSuffix(String suffix) {\n    this.suffix = suffix;\n  }\n\n  public String getPrefix() {\n    return this.prefix;\n  }\n\n  @Override\n  public void setPrefix(String prefix) {\n    this.prefix = prefix;\n  }\n\n  public String getCharset() {\n    return this.charset;\n  }\n\n  @Override\n  public void setCharset(String charset) {\n    this.charset = charset;\n  }\n\n  @Override\n  public String resolveRelativePath(String relativePath, String anchorPath) {\n    return PathUtils.resolveRelativePath(relativePath, anchorPath, this.expectedSeparator);\n  }\n\n  @Override\n  public String createCacheKey(String templateName) {\n    return templateName;\n  }\n\n  @Override\n  public boolean resourceExists(String templateName) {\n    try {\n      return getResource(this.getLocation(templateName)) != null;\n    } catch (MalformedURLException e) {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/ClasspathLoader.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.loader;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\nimport io.pebbletemplates.pebble.utils.PathUtils;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.BufferedReader;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.io.UnsupportedEncodingException;\n\n/**\n * Uses a classloader to find templates located on the classpath.\n *\n * @author mbosecke\n */\npublic class ClasspathLoader implements Loader<String> {\n\n  private static final Logger logger = LoggerFactory.getLogger(ClasspathLoader.class);\n\n  private String prefix;\n\n  private String suffix;\n\n  private String charset = \"UTF-8\";\n\n  private char expectedSeparator = '/';\n\n  private final ClassLoader rcl;\n\n  public ClasspathLoader(ClassLoader classLoader) {\n    this.rcl = classLoader;\n  }\n\n  public ClasspathLoader() {\n    this(ClasspathLoader.class.getClassLoader());\n  }\n\n  @Override\n  public Reader getReader(String templateName) {\n    String location = this.getLocation(templateName);\n\n    logger.debug(\"Looking for template in {}.\", location);\n\n    // perform the lookup\n    InputStream is = this.rcl.getResourceAsStream(location);\n\n    if (is == null) {\n      throw new LoaderException(null, \"Could not find template \\\"\" + location + \"\\\"\");\n    }\n\n    try {\n      return new BufferedReader(new InputStreamReader(is, this.charset));\n    } catch (UnsupportedEncodingException e) {\n    }\n\n    return null;\n  }\n\n  private String getLocation(String templateName) {\n    // append the prefix and make sure prefix ends with a separator character\n    StringBuilder path = new StringBuilder(128);\n    if (this.getPrefix() != null) {\n\n      path.append(this.getPrefix());\n\n      // we do NOT use OS dependent separators here; getResourceAsStream\n      // explicitly requires forward slashes.\n      if (!this.getPrefix().endsWith(Character.toString(this.expectedSeparator))) {\n        path.append(this.expectedSeparator);\n      }\n    }\n    path.append(templateName);\n    if (this.getSuffix() != null) {\n      path.append(this.getSuffix());\n    }\n    return path.toString();\n  }\n\n  public String getSuffix() {\n    return this.suffix;\n  }\n\n  @Override\n  public void setSuffix(String suffix) {\n    this.suffix = suffix;\n  }\n\n  public String getPrefix() {\n    return this.prefix;\n  }\n\n  @Override\n  public void setPrefix(String prefix) {\n    this.prefix = prefix;\n  }\n\n  public String getCharset() {\n    return this.charset;\n  }\n\n  @Override\n  public void setCharset(String charset) {\n    this.charset = charset;\n  }\n\n  @Override\n  public String resolveRelativePath(String relativePath, String anchorPath) {\n    return PathUtils.resolveRelativePath(relativePath, anchorPath, this.expectedSeparator);\n  }\n\n  @Override\n  public String createCacheKey(String templateName) {\n    return templateName;\n  }\n\n  @Override\n  public boolean resourceExists(String templateName) {\n    return this.rcl.getResource(this.getLocation(templateName)) != null;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/DelegatingLoader.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.loader;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\n\nimport java.io.Reader;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * This loader will delegate control to a list of children loaders. This is the default\n * implementation used by Pebble; it delegates to a classpath loader and a file loader to increase\n * the chances of finding templates with varying setups.\n *\n * @author mbosecke\n */\npublic class DelegatingLoader implements Loader<DelegatingLoaderCacheKey> {\n\n  private String prefix;\n\n  private String suffix;\n\n  private String charset = \"UTF-8\";\n\n\n  /**\n   * Children loaders to delegate to. The loaders are used in order and as soon as one of them finds\n   * a template, the others will not be given a chance to do so.\n   */\n  private final List<Loader<?>> loaders;\n\n  /**\n   * Constructor provided with a list of children loaders.\n   *\n   * @param loaders A list of loaders to delegate to\n   */\n  public DelegatingLoader(List<Loader<?>> loaders) {\n    this.loaders = Collections.unmodifiableList(new ArrayList<>(loaders));\n  }\n\n\n  @Override\n  public Reader getReader(DelegatingLoaderCacheKey cacheKey) {\n\n    Reader reader = null;\n\n    final int size = this.loaders.size();\n    for (int i = 0; i < size; i++) {\n      Loader<?> loader = this.loaders.get(i);\n      Object delegatingKey = cacheKey.getDelegatingCacheKeys().get(i);\n      try {\n        reader = this.getReaderInner(loader, delegatingKey);\n        if (reader != null) {\n          break;\n        }\n      } catch (Exception e) {\n        // do nothing\n      }\n    }\n    if (reader == null) {\n      throw new LoaderException(null,\n          \"Could not find template \\\"\" + cacheKey.getTemplateName() + \"\\\"\");\n    }\n\n    return reader;\n  }\n\n  private <T> Reader getReaderInner(Loader<T> delegatingLoader, Object cacheKey) {\n\n    // This unchecked cast is ok, because we ensure that the type of the\n    // cache key corresponds to the loader when we create the key.\n    @SuppressWarnings(\"unchecked\")\n    T castedKey = (T) cacheKey;\n\n    return delegatingLoader.getReader(castedKey);\n  }\n\n  public String getSuffix() {\n    return this.suffix;\n  }\n\n  @Override\n  public void setSuffix(String suffix) {\n    this.suffix = suffix;\n    for (Loader<?> loader : this.loaders) {\n      loader.setSuffix(suffix);\n    }\n  }\n\n  public String getPrefix() {\n    return this.prefix;\n  }\n\n  @Override\n  public void setPrefix(String prefix) {\n    this.prefix = prefix;\n    for (Loader<?> loader : this.loaders) {\n      loader.setPrefix(prefix);\n    }\n  }\n\n  public String getCharset() {\n    return this.charset;\n  }\n\n  @Override\n  public void setCharset(String charset) {\n    this.charset = charset;\n    for (Loader<?> loader : this.loaders) {\n      loader.setCharset(charset);\n    }\n  }\n\n  @Override\n  public String resolveRelativePath(String relativePath, String anchorPath) {\n    if (relativePath == null) {\n      return null;\n    }\n    for (Loader<?> loader : this.loaders) {\n      String path = loader.resolveRelativePath(relativePath, anchorPath);\n      if (path != null) {\n        return path;\n      }\n    }\n    return null;\n  }\n\n  @Override\n  public DelegatingLoaderCacheKey createCacheKey(String templateName) {\n\n    List<Object> keys = new ArrayList<>();\n    for (Loader<?> loader : this.loaders) {\n      keys.add(loader.createCacheKey(templateName));\n    }\n\n    return new DelegatingLoaderCacheKey(keys, templateName);\n  }\n\n  @Override\n  public boolean resourceExists(String templateName) {\n    for (Loader<?> loader : this.loaders) {\n      if (loader.resourceExists(templateName)) {\n        return true;\n      }\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/DelegatingLoaderCacheKey.java",
    "content": "package io.pebbletemplates.pebble.loader;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * The delegating loader cache key is used as the cache key for {@link DelegatingLoader}.\n *\n * <p>\n * The object stores all cache keys of all loaders. Those keys together builds the key for the\n * delegating loader.\n *\n * @author Thomas Hunziker\n */\npublic final class DelegatingLoaderCacheKey {\n\n  private final List<Object> delegatingCacheKeys;\n\n  private final String templateName;\n\n  private final int hashCode;\n\n  DelegatingLoaderCacheKey(final List<Object> delegatingCacheKeys, final String templateName) {\n    this.delegatingCacheKeys = Collections.unmodifiableList(new ArrayList<>(delegatingCacheKeys));\n    this.templateName = templateName;\n    this.hashCode = this.caclulateHashCode();\n  }\n\n  public String getTemplateName() {\n    return templateName;\n  }\n\n  public List<Object> getDelegatingCacheKeys() {\n    return delegatingCacheKeys;\n  }\n\n  private int caclulateHashCode() {\n    final int prime = 31;\n    int result = 1;\n    result = prime * result + ((delegatingCacheKeys == null) ? 0 : delegatingCacheKeys.hashCode());\n    result = prime * result + ((templateName == null) ? 0 : templateName.hashCode());\n    return result;\n  }\n\n  @Override\n  public int hashCode() {\n    return this.hashCode;\n  }\n\n  @Override\n  public boolean equals(Object obj) {\n    if (this == obj) {\n      return true;\n    }\n    if (obj == null) {\n      return false;\n    }\n    if (getClass() != obj.getClass()) {\n      return false;\n    }\n    DelegatingLoaderCacheKey other = (DelegatingLoaderCacheKey) obj;\n    if (delegatingCacheKeys == null) {\n      if (other.delegatingCacheKeys != null) {\n        return false;\n      }\n    } else if (!delegatingCacheKeys.equals(other.delegatingCacheKeys)) {\n      return false;\n    }\n    if (templateName == null) {\n      return other.templateName == null;\n    } else {\n      return templateName.equals(other.templateName);\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/FileLoader.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.loader;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\nimport io.pebbletemplates.pebble.utils.PathUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.*;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/**\n * This loader searches for a file located anywhere on the filesystem. It uses java.io.File to\n * perform the lookup.\n *\n * @author mbosecke\n */\npublic class FileLoader implements Loader<String> {\n\n  private static final Logger logger = LoggerFactory.getLogger(FileLoader.class);\n\n  private String prefix;\n  private String suffix;\n  private String charset = \"UTF-8\";\n\n  public FileLoader(String prefix) {\n    this.setPrefix(prefix);\n  }\n\n  @Override\n  public Reader getReader(String templateName) {\n    File file = this.getFile(templateName);\n    try {\n      InputStream is = new FileInputStream(file);\n      return new BufferedReader(new InputStreamReader(is, this.charset));\n    } catch (FileNotFoundException e) {\n      throw new LoaderException(e, String.format(\"Could not find template [prefix='%s', templateName='%s']\", this.prefix, templateName));\n    } catch (UnsupportedEncodingException e) {\n      throw new LoaderException(e, String.format(\"Invalid charset '%s'\", this.charset));\n    }\n  }\n\n  private File getFile(String templateName) {\n    templateName = templateName + (this.getSuffix() == null ? \"\" : this.getSuffix());\n    templateName = PathUtils.sanitize(templateName, File.separatorChar);\n\n    Path path = Paths.get(this.getPrefix(), templateName);\n    logger.trace(\"Looking for template in {}.\", path);\n\n    this.checkIfDirectoryTraversal(templateName);\n    return path.toFile();\n  }\n\n  public String getSuffix() {\n    return this.suffix;\n  }\n\n  @Override\n  public void setSuffix(String suffix) {\n    this.suffix = suffix;\n  }\n\n  public String getPrefix() {\n    return this.prefix;\n  }\n\n  @Override\n  public void setPrefix(String prefix) {\n    if (prefix == null) {\n      throw new LoaderException(null, \"Prefix cannot be null\");\n    }\n    String trimmedPrefix = prefix.trim();\n    if (trimmedPrefix.isEmpty()) {\n      throw new LoaderException(null, \"Prefix cannot be empty\");\n    }\n    if (!Paths.get(trimmedPrefix).isAbsolute()) {\n      throw new LoaderException(null, \"Prefix must be an absolute path\");\n    }\n    this.prefix = trimmedPrefix;\n  }\n\n  public String getCharset() {\n    return this.charset;\n  }\n\n  @Override\n  public void setCharset(String charset) {\n    this.charset = charset;\n  }\n\n  @Override\n  public String resolveRelativePath(String relativePath, String anchorPath) {\n    return PathUtils.resolveRelativePath(relativePath, anchorPath, File.separatorChar);\n  }\n\n  @Override\n  public String createCacheKey(String templateName) {\n    return templateName;\n  }\n\n  @Override\n  public boolean resourceExists(String templateName) {\n    return this.getFile(templateName).exists();\n  }\n\n  private void checkIfDirectoryTraversal(String templateName) {\n    Path baseDirPath = Paths.get(prefix);\n    Path userPath = Paths.get(templateName);\n    if (userPath.isAbsolute()) {\n      throw new LoaderException(null, String.format(\"templateName '%s' must be relative\", templateName));\n    }\n\n    // Join the two paths together, then normalize so that any \"..\" elements\n    // in the userPath can remove parts of baseDirPath.\n    // (e.g. \"/foo/bar/baz\" + \"../attack\" -> \"/foo/bar/attack\")\n    Path resolvedPath = baseDirPath.resolve(userPath).normalize();\n\n    // Make sure the resulting path is still within the required directory.\n    // (In the example above, \"/foo/bar/attack\" is not.)\n    if (!resolvedPath.startsWith(baseDirPath)) {\n      throw new LoaderException(null, String.format(\"template is not in the base directory path [baseDir='%s', templateName='%s']\", this.prefix, templateName));\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/Loader.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.loader;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\n\nimport java.io.Reader;\n\n/**\n * Interface used to find templates for Pebble. Different implementations can use different\n * techniques for finding templates such as looking on the classpath, looking in a database, using a\n * servlet context, etc.\n *\n * @author mbosecke\n */\npublic interface Loader<T> {\n\n  /**\n   * The reader which will be used by Pebble to read the contents of the template.\n   *\n   * @param cacheKey the cache key to use to load create the reader.\n   * @return A reader object, or {@code null} if the template does not exist\n   */\n  Reader getReader(T cacheKey);\n\n  /**\n   * A method for end users to change the charset used by the loader.\n   *\n   * @param charset Character set used by the loader when building a reader object\n   */\n  void setCharset(String charset);\n\n  /**\n   * Optional prefix to help find templates, ex \"/WEB-INF/templates/\" or \"database_schema.\"\n   *\n   * @param prefix Prefix to help find templates\n   */\n  void setPrefix(String prefix);\n\n  /**\n   * Optional suffix to help find templates, ex \".html\", \".peb\"\n   *\n   * @param suffix Suffix to attach to template names\n   */\n  void setSuffix(String suffix);\n\n  /**\n   * Resolves the given {@code relativePath} based on the given {@code anchorPath}.\n   *\n   * <p>\n   * A path is considered as relative when it starts either with '..' or '.' and followed either by\n   * a '/' or '\\\\' otherwise the assumption is that the provided path is an absolute path.\n   *\n   * @param relativePath the relative path which should be resolved.\n   * @param anchorPath the anchor path based on which the relative path should be resolved on.\n   * @return the resolved path or {@code null} when the path could not be resolved.\n   */\n  String resolveRelativePath(String relativePath, String anchorPath);\n\n  /**\n   * This method resolves the given template name to a unique object which can be used as the key\n   * within the {@link PebbleEngine#getTemplateCache()}. The returned object will be passed with\n   * {@link #getReader(Object)}.\n   *\n   * <p>\n   * The resolve method can eventually add information to the cache key from the context (e.g. user\n   * session information, servlet request etc.).\n   *\n   * <p>\n   * As a concrete example if the loader loads a template created by a user form the database the\n   * template name itself is not uniquely identify the template. The identification of the template\n   * requires also the user which created the template. Hence for the key the user id and the\n   * template name should be used. So the cache key is enhanced by some contextual information.\n   *\n   * <p>\n   * The implementor of the method can add as many additional contextual information to the returned\n   * object. However the following things needs to be considered:\n   * <ul>\n   * <li>This method will be called on each\n   * {@link PebbleEngine#getTemplate(String)}. Hence the implementation needs to be fast and\n   * eventually use some caching for the lookup process.</li>\n   * <li>The returned object is used within a cache and hence needs to\n   * implement {@link Object#equals(Object)} and {@link Object#hashCode()}.</li>\n   * <li>The object is kept in memory and hence it should not be to memory\n   * heavy.</li>\n   * </ul>\n   *\n   * <p>\n   * Depending on this implementation the {@link PebbleEngine#getTemplateCache()} should be tuned in\n   * a way it can operate optimal. E.g. when the number of potential templates is infinite the cache\n   * should evict some templates at some point in time otherwise the stability of the memory is not\n   * given anymore.\n   *\n   * @param templateName The name of the template\n   * @return Returns the cache key\n   */\n  T createCacheKey(String templateName);\n\n  boolean resourceExists(String templateName);\n}"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/MemoryLoader.java",
    "content": "package io.pebbletemplates.pebble.loader;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\n\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MemoryLoader implements Loader<String> {\n    private final List<TemplateDefinition> templateDefinitions = new ArrayList<>();\n\n    @Override\n    public Reader getReader(String templateName) {\n        String content = \"\";\n        for (TemplateDefinition templateDefinition : this.templateDefinitions) {\n            if (templateDefinition.templateName.equals(templateName)) {\n                content = templateDefinition.content;\n                break;\n            }\n        }\n\n        if (content.isEmpty()) {\n            throw new LoaderException(null, \"Could not find template \\\"\" + templateName + \"\\\"\");\n        }\n\n        return new StringReader(content);\n    }\n\n    public void addTemplate(String templateName, String content) {\n        if (templateName == null) {\n            throw new IllegalArgumentException(\"templateName cannot be null\");\n        }\n        if (content == null) {\n            throw new IllegalArgumentException(\"content cannot be null\");\n        }\n        this.templateDefinitions.add(new TemplateDefinition(templateName, content));\n    }\n\n    public List<TemplateDefinition> getTemplates() {\n        return this.templateDefinitions;\n    }\n\n    @Override\n    public void setSuffix(String suffix) {\n    }\n\n    @Override\n    public void setPrefix(String prefix) {\n    }\n\n    @Override\n    public void setCharset(String charset) {\n    }\n\n    @Override\n    public String resolveRelativePath(String relativePath, String anchorPath) {\n        return relativePath; // hierarchy is flat\n    }\n\n    @Override\n    public String createCacheKey(String templateName) {\n        return templateName;\n    }\n\n    @Override\n    public boolean resourceExists(String templateName) {\n        for (TemplateDefinition templateDefinition : this.templateDefinitions) {\n            if (templateDefinition.templateName.equals(templateName)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static class TemplateDefinition {\n        public final String templateName;\n        public final String content;\n\n        public TemplateDefinition(String templateName,\n                                  String content) {\n            this.templateName = templateName;\n            this.content = content;\n        }\n    }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/Servlet5Loader.java",
    "content": "package io.pebbletemplates.pebble.loader;\n\nimport jakarta.servlet.ServletContext;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\n/**\n * Loader that uses a servlet context to find templates. Requires Jakarta Servlet 5.0 or newer.\n *\n * @author mbosecke\n * @author chkal\n */\npublic class Servlet5Loader extends AbstractServletLoader {\n\n  private final ServletContext context;\n\n  public Servlet5Loader(ServletContext context) {\n    this.context = context;\n  }\n\n  @Override\n  protected InputStream getResourceAsStream(String location) {\n    return context.getResourceAsStream(location);\n  }\n\n  @Override\n  protected URL getResource(String location) throws MalformedURLException {\n    return context.getResource(location);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/ServletLoader.java",
    "content": "package io.pebbletemplates.pebble.loader;\n\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport javax.servlet.ServletContext;\n\n/**\n * Loader that uses a servlet context to find templates.\n *\n * @author mbosecke\n * @author chkal\n */\npublic class ServletLoader extends AbstractServletLoader {\n\n  private final ServletContext context;\n\n  public ServletLoader(ServletContext context) {\n    this.context = context;\n  }\n\n  @Override\n  protected InputStream getResourceAsStream(String location) {\n    return context.getResourceAsStream(location);\n  }\n\n  @Override\n  protected URL getResource(String location) throws MalformedURLException {\n    return context.getResource(location);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/loader/StringLoader.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.loader;\n\nimport java.io.Reader;\nimport java.io.StringReader;\n\n/**\n * This loader is not intended to be used in a production system; it is primarily for testing and\n * debugging. Many tags do not work when using this loader, such as \"extends\", \"imports\",\n * \"include\".\n */\npublic class StringLoader implements Loader<String> {\n\n  @Override\n  public Reader getReader(String templateName) {\n    return new StringReader(templateName);\n  }\n\n  @Override\n  public void setPrefix(String prefix) {\n\n  }\n\n  @Override\n  public void setSuffix(String suffix) {\n\n  }\n\n  @Override\n  public void setCharset(String charset) {\n\n  }\n\n  @Override\n  public String resolveRelativePath(String relativePath, String anchorPath) {\n    return null;\n  }\n\n  @Override\n  public String createCacheKey(String templateName) {\n    return templateName;\n  }\n\n  @Override\n  public boolean resourceExists(String templateName) {\n    return true;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/AbstractRenderableNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\npublic abstract class AbstractRenderableNode implements RenderableNode {\n\n  private int lineNumber;\n\n  @Override\n  public abstract void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException;\n\n  @Override\n  public abstract void accept(NodeVisitor visitor);\n\n  public AbstractRenderableNode() {\n  }\n\n  public AbstractRenderableNode(int lineNumber) {\n    this.setLineNumber(lineNumber);\n  }\n\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public void setLineNumber(int lineNumber) {\n    this.lineNumber = lineNumber;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/ArgumentsNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NamedArguments;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class ArgumentsNode implements Node {\n\n  private final List<NamedArgumentNode> namedArgs;\n\n  private final List<PositionalArgumentNode> positionalArgs;\n\n  private final int lineNumber;\n\n  public ArgumentsNode(List<PositionalArgumentNode> positionalArgs,\n      List<NamedArgumentNode> namedArgs,\n      int lineNumber) {\n    this.positionalArgs = positionalArgs;\n    this.namedArgs = namedArgs;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public List<NamedArgumentNode> getNamedArgs() {\n    return this.namedArgs;\n  }\n\n  public List<PositionalArgumentNode> getPositionalArgs() {\n    return this.positionalArgs;\n  }\n\n  /**\n   * Using hints from the filter/function/test/macro it will convert an ArgumentMap (which holds\n   * both positional and named arguments) into a regular Map that the filter/function/test/macro is\n   * expecting.\n   *\n   * @param self The template implementation\n   * @param context The evaluation context\n   * @param invocableWithNamedArguments The named arguments object\n   * @return Returns a map representaion of the arguments\n   */\n  public Map<String, Object> getArgumentMap(PebbleTemplateImpl self, EvaluationContextImpl context,\n                                            NamedArguments invocableWithNamedArguments) {\n    Map<String, Object> result = new HashMap<>();\n    List<String> argumentNames = invocableWithNamedArguments.getArgumentNames();\n\n    if (argumentNames == null || argumentNames.isEmpty()) {\n      /* Some functions such as min and max use un-named varags */\n      if (this.positionalArgs != null && !this.positionalArgs.isEmpty()) {\n        for (int i = 0; i < this.positionalArgs.size(); i++) {\n          result.put(String.valueOf(i), this.positionalArgs.get(i).getValueExpression().evaluate(self, context));\n        }\n      }\n\n      // Support dynamic varargs (Issue #740)\n      if (this.namedArgs != null) {\n        for (NamedArgumentNode arg: this.namedArgs) {\n          Object value = arg.getValueExpression() == null ? null : arg.getValueExpression().evaluate(self, context);\n          result.put(arg.getName(), value);\n        }\n      }\n    } else {\n      if (this.positionalArgs != null) {\n        int nameIndex = 0;\n        for (PositionalArgumentNode arg: this.positionalArgs) {\n          if (argumentNames.size() <= nameIndex) {\n            throw new PebbleException(null, \"The argument at position \" + (nameIndex + 1)\n                + \" is not allowed. Only \" + argumentNames.size() + \" argument(s) are allowed.\",\n                this.lineNumber, self.getName());\n          }\n\n          result.put(argumentNames.get(nameIndex), arg.getValueExpression().evaluate(self, context));\n          nameIndex++;\n        }\n      }\n\n      if (this.namedArgs != null) {\n        for (NamedArgumentNode arg: this.namedArgs) {\n          // check if user used an incorrect name\n          if (!argumentNames.contains(arg.getName())) {\n            throw new PebbleException(null,\n                \"The following named argument does not exist: \" + arg.getName(),\n                this.lineNumber, self.getName());\n          }\n          Object value = arg.getValueExpression() == null ? null : arg.getValueExpression().evaluate(self, context);\n          result.put(arg.getName(), value);\n        }\n      }\n    }\n\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return this.positionalArgs.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/AutoEscapeNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\npublic class AutoEscapeNode extends AbstractRenderableNode {\n\n  private final BodyNode body;\n\n  private final String strategy;\n\n  private final boolean active;\n\n  public AutoEscapeNode(int lineNumber, BodyNode body, boolean active, String strategy) {\n    super(lineNumber);\n    this.body = body;\n    this.strategy = strategy;\n    this.active = active;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    this.body.render(self, writer, context);\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public BodyNode getBody() {\n    return this.body;\n  }\n\n  public String getStrategy() {\n    return this.strategy;\n  }\n\n  public boolean isActive() {\n    return this.active;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/BlockNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.Block;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\npublic class BlockNode extends AbstractRenderableNode {\n\n  private final BodyNode body;\n\n  private String name;\n\n  public BlockNode(int lineNumber, String name) {\n    this(lineNumber, name, null);\n  }\n\n  public BlockNode(int lineNumber, String name, BodyNode body) {\n    super(lineNumber);\n    this.body = body;\n    this.name = name;\n  }\n\n  @Override\n  public void render(final PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    self.block(writer, context, this.name, false);\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Block getBlock() {\n    return new Block() {\n\n      @Override\n      public String getName() {\n        return BlockNode.this.name;\n      }\n\n      @Override\n      public void evaluate(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n          throws IOException {\n        BlockNode.this.body.render(self, writer, context);\n      }\n    };\n  }\n\n  public BodyNode getBody() {\n    return this.body;\n  }\n\n  public String getName() {\n    return this.name;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/BodyNode.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class BodyNode extends AbstractRenderableNode {\n\n  private final List<RenderableNode> children;\n\n  /**\n   * When a template extends a parent template there are very few nodes in the child that should\n   * actually get rendered such as set and import. All others should be ignored.\n   */\n  private boolean onlyRenderInheritanceSafeNodes = false;\n\n  public BodyNode(int lineNumber, List<RenderableNode> children) {\n    super(lineNumber);\n    this.children = children;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    for (RenderableNode child: this.children) {\n      if (this.onlyRenderInheritanceSafeNodes && context.getHierarchy().getParent() != null) {\n        if (!nodesToRenderInChild.contains(child.getClass())) {\n          continue;\n        }\n      }\n      child.render(self, writer, context);\n    }\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public List<RenderableNode> getChildren() {\n    return this.children;\n  }\n\n  public boolean isOnlyRenderInheritanceSafeNodes() {\n    return this.onlyRenderInheritanceSafeNodes;\n  }\n\n  public void setOnlyRenderInheritanceSafeNodes(boolean onlyRenderInheritanceSafeNodes) {\n    this.onlyRenderInheritanceSafeNodes = onlyRenderInheritanceSafeNodes;\n  }\n\n  private static List<Class<? extends Node>> nodesToRenderInChild = new ArrayList<>();\n\n  static {\n    nodesToRenderInChild.add(SetNode.class);\n    nodesToRenderInChild.add(ImportNode.class);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/CacheNode.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.cache.CacheKey;\nimport io.pebbletemplates.pebble.cache.PebbleCache;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.LimitedSizeWriter;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.concurrent.CompletionException;\n\n/**\n * Node for the cache tag\n *\n * @author Eric Bussieres\n */\npublic class CacheNode extends AbstractRenderableNode {\n\n  private final BodyNode body;\n\n  private final Expression<?> name;\n\n  public CacheNode(int lineNumber, Expression<?> name, BodyNode body) {\n    super(lineNumber);\n    this.body = body;\n    this.name = name;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer,\n                     EvaluationContextImpl context) throws IOException {\n    try {\n      final String body;\n      PebbleCache<CacheKey, Object> tagCache = context.getTagCache();\n      CacheKey key = new CacheKey(this, (String) this.name.evaluate(self, context),\n          context.getLocale());\n      body = (String) context.getTagCache().computeIfAbsent(key, k -> {\n        try {\n          return this.render(self, context);\n        } catch (IOException e) {\n          throw new RuntimeException(e);\n        }\n      });\n      writer.write(body);\n    } catch (CompletionException e) {\n      throw new PebbleException(e, \"Could not render cache block [\" + this.name + \"]\");\n    }\n  }\n\n  private String render(final PebbleTemplateImpl self, final EvaluationContextImpl context)\n      throws IOException {\n    Writer tempWriter = LimitedSizeWriter.from(new StringWriter(), context);\n    CacheNode.this.body.render(self, tempWriter, context);\n\n    return tempWriter.toString();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/EmbedNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.expression.MapExpression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\npublic class EmbedNode extends AbstractRenderableNode {\n\n  private final Expression<?> includeExpression;\n\n  private final MapExpression mapExpression;\n\n  private final List<BlockNode> nodes;\n\n  public EmbedNode(int lineNumber, Expression<?> includeExpression, MapExpression mapExpression, List<BlockNode> nodes) {\n    super(lineNumber);\n    this.includeExpression = includeExpression;\n    this.mapExpression = mapExpression;\n    this.nodes = nodes;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    String templateName = (String) this.includeExpression.evaluate(self, context);\n\n    Map<?, ?> map = Collections.emptyMap();\n    if (this.mapExpression != null) {\n      map = this.mapExpression.evaluate(self, context);\n    }\n\n    if (templateName == null) {\n      throw new PebbleException(\n          null,\n          \"The template name in an embed tag evaluated to NULL. If the template name is static, make sure to wrap it in quotes.\",\n          this.getLineNumber(), self.getName());\n    }\n    self.embedTemplate(getLineNumber(), writer, context, templateName, map, nodes);\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/ExtendsNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.Writer;\n\npublic class ExtendsNode extends AbstractRenderableNode {\n\n  Expression<?> parentExpression;\n\n  public ExtendsNode(int lineNumber, Expression<?> parentExpression) {\n    super(lineNumber);\n    this.parentExpression = parentExpression;\n  }\n\n  @Override\n  public void render(final PebbleTemplateImpl self, Writer writer,\n                     final EvaluationContextImpl context) {\n    self.setParent(context, (String) this.parentExpression.evaluate(self, context));\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getParentExpression() {\n    return this.parentExpression;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/FlushNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\npublic class FlushNode extends AbstractRenderableNode {\n\n  public FlushNode(int lineNumber) {\n    super(lineNumber);\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    writer.flush();\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/ForNode.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.fornode.LazyLength;\nimport io.pebbletemplates.pebble.node.fornode.LazyRevIndex;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.template.ScopeChain;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.lang.reflect.Array;\nimport java.util.Enumeration;\nimport java.util.Iterator;\nimport java.util.Map;\n\n/**\n * Represents a \"for\" loop within the template.\n *\n * @author mbosecke\n */\npublic class ForNode extends AbstractRenderableNode {\n\n  private final String variableName;\n\n  private final Expression<?> iterableExpression;\n\n  private final BodyNode body;\n\n  private final BodyNode elseBody;\n\n  public ForNode(int lineNumber, String variableName, Expression<?> iterableExpression,\n      BodyNode body,\n      BodyNode elseBody) {\n    super(lineNumber);\n    this.variableName = variableName;\n    this.iterableExpression = iterableExpression;\n    this.body = body;\n    this.elseBody = elseBody;\n  }\n\n  public static class LoopVariables {\n    private boolean first, last;\n    private LazyLength length;\n    private int index;\n    private LazyRevIndex revindex;\n\n    @Override\n    public String toString() {\n      return \"{last=\" + last + \", length=\" + length + \", index=\" + index + \", revindex=\" + revindex + \", first=\" + first + \"}\";\n    }\n\n    public boolean isFirst() {\n      return first;\n    }\n\n    public boolean isLast() {\n      return last;\n    }\n\n    public LazyLength getLength() {\n      return length;\n    }\n\n    public int getIndex() {\n      return index;\n    }\n\n    public LazyRevIndex getRevindex() {\n      return revindex;\n    }\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    final Object iterableEvaluation = this.iterableExpression.evaluate(self, context);\n    Iterable<?> iterable;\n\n    if (iterableEvaluation == null) {\n      return;\n    }\n\n    iterable = this.toIterable(iterableEvaluation);\n\n    if (iterable == null) {\n      throw new PebbleException(null,\n          \"Not an iterable object. Value = [\" + iterableEvaluation.toString() + \"]\",\n          this.getLineNumber(), self.getName());\n    }\n\n    Iterator<?> iterator = iterable.iterator();\n\n    if (iterator.hasNext()) {\n\n      ScopeChain scopeChain = context.getScopeChain();\n      scopeChain.pushScope();\n\n      LazyLength length = new LazyLength(iterableEvaluation);\n\n      int index = 0;\n\n      LoopVariables loop = null;\n\n      boolean usingExecutorService = context.getExecutorService() != null;\n\n      while (iterator.hasNext()) {\n\n        /*\n         * If the user is using an executor service (i.e. parallel\n         * node), we must create a new map with every iteration instead\n         * of re-using the same one; it's imperative that each thread\n         * would get it's own distinct copy of the context.\n         */\n        if (index == 0 || usingExecutorService) {\n          loop = new LoopVariables();\n          loop.first = index == 0;\n          loop.last = !iterator.hasNext();\n          loop.length = length;\n        } else if (index == 1) {\n          // second iteration\n          loop.first = false;\n        }\n\n        loop.revindex = new LazyRevIndex(index, length);\n        loop.index = index++;\n        scopeChain.put(\"loop\", loop);\n        scopeChain.put(this.variableName, iterator.next());\n\n        // last iteration\n        if (!iterator.hasNext()) {\n          loop.last = true;\n        }\n\n        this.body.render(self, writer, context);\n      }\n\n      scopeChain.popScope();\n\n    } else if (this.elseBody != null) {\n      this.elseBody.render(self, writer, context);\n    }\n\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public String getIterationVariable() {\n    return this.variableName;\n  }\n\n  public Expression<?> getIterable() {\n    return this.iterableExpression;\n  }\n\n  public BodyNode getBody() {\n    return this.body;\n  }\n\n  public BodyNode getElseBody() {\n    return this.elseBody;\n  }\n\n  @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n  private Iterable<Object> toIterable(final Object obj) {\n\n    Iterable<Object> result = null;\n\n    if (obj instanceof Iterable<?>) {\n      result = (Iterable<Object>) obj;\n    } else if (obj instanceof Map) {\n      // raw type\n      result = ((Map) obj).entrySet();\n    } else if (obj.getClass().isArray()) {\n      result = new ArrayIterable(obj);\n    } else if (obj instanceof Enumeration) {\n      result = new EnumerationIterable((Enumeration) obj);\n    }\n\n    return result;\n  }\n\n  /**\n   * Adapts an array to an Iterable\n   */\n  private class ArrayIterable implements Iterable<Object> {\n\n    private Object obj;\n\n    ArrayIterable(Object array) {\n      this.obj = array;\n    }\n\n    @Override\n    public Iterator<Object> iterator() {\n      return new Iterator<Object>() {\n\n        private int index = 0;\n\n        private final int length = Array.getLength(ArrayIterable.this.obj);\n\n        @Override\n        public boolean hasNext() {\n          return this.index < this.length;\n        }\n\n        @Override\n        public Object next() {\n          return Array.get(ArrayIterable.this.obj, this.index++);\n        }\n\n        @Override\n        public void remove() {\n          throw new UnsupportedOperationException();\n        }\n      };\n    }\n  }\n\n  /**\n   * Adapts an Enumeration to an Iterable\n   */\n  private class EnumerationIterable implements Iterable<Object> {\n\n    private Enumeration<Object> obj;\n\n    EnumerationIterable(Enumeration<Object> enumeration) {\n      this.obj = enumeration;\n    }\n\n    @Override\n    public Iterator<Object> iterator() {\n      return new Iterator<Object>() {\n\n        @Override\n        public boolean hasNext() {\n          return EnumerationIterable.this.obj.hasMoreElements();\n        }\n\n        @Override\n        public Object next() {\n          return EnumerationIterable.this.obj.nextElement();\n        }\n\n        @Override\n        public void remove() {\n          throw new UnsupportedOperationException();\n        }\n      };\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/FromNode.java",
    "content": "package io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.Pair;\n\nimport java.io.Writer;\nimport java.util.List;\n\n/**\n * From Node for\n *\n * <p>{% from \"templateName\" import macroName as alias %}<p>\n *\n * @author yanxiyue\n */\npublic class FromNode extends AbstractRenderableNode {\n\n  private final Expression<?> fromExpression;\n  private final List<Pair<String, String>> namedMacros;\n\n  public FromNode(int lineNumber, Expression<?> fromExpression,\n      List<Pair<String, String>> namedMacros) {\n    super(lineNumber);\n    this.fromExpression = fromExpression;\n    this.namedMacros = namedMacros;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context) {\n    String templateName = (String) fromExpression.evaluate(self, context);\n    self.importNamedMacrosFromTemplate(templateName, namedMacros);\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/FunctionOrMacroNameNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class FunctionOrMacroNameNode implements Expression<String> {\n\n  private final String name;\n\n  private final int lineNumber;\n\n  public FunctionOrMacroNameNode(String name, int lineNumber) {\n    this.name = name;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public String getName() {\n    return this.name;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/IfNode.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.Pair;\nimport io.pebbletemplates.pebble.utils.TypeUtils;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.List;\n\nimport static io.pebbletemplates.pebble.utils.TypeUtils.compatibleCast;\n\npublic class IfNode extends AbstractRenderableNode {\n\n  private final List<Pair<Expression<?>, BodyNode>> conditionsWithBodies;\n\n  private final BodyNode elseBody;\n\n  public IfNode(int lineNumber, List<Pair<Expression<?>, BodyNode>> conditionsWithBodies) {\n    this(lineNumber, conditionsWithBodies, null);\n  }\n\n  public IfNode(int lineNumber, List<Pair<Expression<?>, BodyNode>> conditionsWithBodies,\n      BodyNode elseBody) {\n    super(lineNumber);\n    this.conditionsWithBodies = conditionsWithBodies;\n    this.elseBody = elseBody;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n\n    boolean satisfied = false;\n    for (Pair<Expression<?>, BodyNode> ifStatement: this.conditionsWithBodies) {\n\n      Expression<?> conditionalExpression = ifStatement.getLeft();\n\n      try {\n\n        Object result = conditionalExpression.evaluate(self, context);\n\n        if (result != null) {\n          if (result instanceof Boolean\n                  || result instanceof Number\n                  || result instanceof String) {\n            satisfied = TypeUtils.compatibleCast(result, Boolean.class);\n          } else {\n            throw new PebbleException(\n                      null,\n                      String.format(\n                              \"Unsupported value type %s. Expected Boolean, String, Number in \\\"if\\\" statement\",\n                              result.getClass().getSimpleName()),\n                      this.getLineNumber(),\n                      self.getName());\n          }\n\n        } else if (context.isStrictVariables()) {\n          throw new PebbleException(null,\n              \"null value given to if statement and strict variables is set to true\",\n              this.getLineNumber(), self.getName());\n        }\n\n      } catch (RuntimeException ex) {\n        throw new PebbleException(ex, \"Wrong operand(s) type in conditional expression\",\n            this.getLineNumber(), self.getName());\n      }\n\n      if (satisfied) {\n        ifStatement.getRight().render(self, writer, context);\n        break;\n      }\n    }\n\n    if (!satisfied && this.elseBody != null) {\n      this.elseBody.render(self, writer, context);\n    }\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public List<Pair<Expression<?>, BodyNode>> getConditionsWithBodies() {\n    return this.conditionsWithBodies;\n  }\n\n  public BodyNode getElseBody() {\n    return this.elseBody;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/ImportNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.MacroAttributeProvider;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.Writer;\n\npublic class ImportNode extends AbstractRenderableNode {\n\n  private final Expression<?> importExpression;\n  private final String alias;\n\n  public ImportNode(int lineNumber, Expression<?> importExpression, String alias) {\n    super(lineNumber);\n    this.importExpression = importExpression;\n    this.alias = alias;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context) {\n    String templateName = (String) this.importExpression.evaluate(self, context);\n    if (this.alias != null) {\n      self.importNamedTemplate(context, templateName, this.alias);\n\n      // put the imported template into scope\n      PebbleTemplateImpl template = self.getNamedImportedTemplate(context, this.alias);\n      context.getScopeChain().put(this.alias, new MacroAttributeProvider(template));\n\n    } else {\n      self.importTemplate(context, templateName);\n    }\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getImportExpression() {\n    return this.importExpression;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/IncludeNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.expression.MapExpression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.Collections;\nimport java.util.Map;\n\npublic class IncludeNode extends AbstractRenderableNode {\n\n  private final Expression<?> includeExpression;\n\n  private final MapExpression mapExpression;\n\n  public IncludeNode(int lineNumber, Expression<?> includeExpression, MapExpression mapExpression) {\n    super(lineNumber);\n    this.includeExpression = includeExpression;\n    this.mapExpression = mapExpression;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    String templateName = (String) this.includeExpression.evaluate(self, context);\n\n    Map<?, ?> map = Collections.emptyMap();\n    if (this.mapExpression != null) {\n      map = this.mapExpression.evaluate(self, context);\n    }\n\n    if (templateName == null) {\n      throw new PebbleException(\n          null,\n          \"The template name in an include tag evaluated to NULL. If the template name is static, make sure to wrap it in quotes.\",\n          this.getLineNumber(), self.getName());\n    }\n    self.includeTemplate(writer, context, templateName, map);\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getIncludeExpression() {\n    return this.includeExpression;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/MacroNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.Macro;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.template.ScopeChain;\nimport io.pebbletemplates.pebble.utils.LimitedSizeWriter;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class MacroNode extends AbstractRenderableNode {\n\n  private final String name;\n\n  private final ArgumentsNode args;\n\n  private final BodyNode body;\n\n  public MacroNode(String name, ArgumentsNode args, BodyNode body) {\n    this.name = name;\n    this.args = args;\n    this.body = body;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context) {\n    // do nothing\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Macro getMacro() {\n    return new Macro() {\n\n      @Override\n      public List<String> getArgumentNames() {\n        List<String> names = new ArrayList<>();\n        for (NamedArgumentNode arg: MacroNode.this.getArgs().getNamedArgs()) {\n          names.add(arg.getName());\n        }\n        return names;\n      }\n\n      @Override\n      public String getName() {\n        return MacroNode.this.name;\n      }\n\n      @Override\n      public String call(PebbleTemplateImpl self, EvaluationContextImpl context,\n          Map<String, Object> macroArgs) {\n        Writer writer = LimitedSizeWriter.from(new StringWriter(), context);\n        ScopeChain scopeChain = context.getScopeChain();\n\n        // scope for default arguments\n        scopeChain.pushLocalScope();\n        \n        // global vars provided by extensions\n        context.getExtensionRegistry().getGlobalVariables().forEach(scopeChain::put);\n        \n        for (NamedArgumentNode arg: MacroNode.this.getArgs().getNamedArgs()) {\n          Expression<?> valueExpression = arg.getValueExpression();\n          if (valueExpression == null) {\n            scopeChain.put(arg.getName(), null);\n          } else {\n            scopeChain.put(arg.getName(), arg.getValueExpression().evaluate(self, context));\n          }\n        }\n\n        // scope for user provided arguments\n        scopeChain.pushScope(macroArgs);\n\n        try {\n          MacroNode.this.getBody().render(self, writer, context);\n        } catch (IOException e) {\n          throw new RuntimeException(\"Could not evaluate macro [\" + MacroNode.this.name + \"]\", e);\n        }\n\n        scopeChain.popScope(); // user arguments\n        scopeChain.popScope(); // default arguments\n\n        return writer.toString();\n      }\n\n    };\n  }\n\n  public BodyNode getBody() {\n    return this.body;\n  }\n\n  public ArgumentsNode getArgs() {\n    return this.args;\n  }\n\n  public String getName() {\n    return this.name;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/NamedArgumentNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\n\npublic class NamedArgumentNode implements Node {\n\n  private final Expression<?> value;\n\n  private final String name;\n\n  public NamedArgumentNode(String name, Expression<?> value) {\n    this.name = name;\n    this.value = value;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getValueExpression() {\n    return this.value;\n  }\n\n  public String getName() {\n    return this.name;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/Node.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\n\npublic interface Node {\n\n  void accept(NodeVisitor visitor);\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/ParallelNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.FutureWriter;\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class ParallelNode extends AbstractRenderableNode {\n\n  private final Logger logger = LoggerFactory.getLogger(ParallelNode.class);\n\n  private final BodyNode body;\n\n  /**\n   * If the user is using the parallel tag but doesn't provide an ExecutorService we will warn them\n   * that this tag will essentially be ignored but it's important that we only warn them once\n   * because this tag may show up in a loop.\n   */\n  private boolean hasWarnedAboutNonExistingExecutorService = false;\n\n  public ParallelNode(int lineNumber, BodyNode body) {\n    super(lineNumber);\n    this.body = body;\n  }\n\n  @Override\n  public void render(final PebbleTemplateImpl self, Writer writer,\n                     final EvaluationContextImpl context)\n      throws IOException {\n\n    ExecutorService es = context.getExecutorService();\n\n    if (es == null) {\n\n      if (!this.hasWarnedAboutNonExistingExecutorService) {\n        this.logger.info(String.format(\n            \"The parallel tag was used [%s:%d] but no ExecutorService was provided. The parallel tag will be ignored \"\n                + \"and it's contents will be rendered in sequence with the rest of the template.\",\n            self.getName(), this.getLineNumber()));\n        this.hasWarnedAboutNonExistingExecutorService = true;\n      }\n\n      /*\n       * If user did not provide an ExecutorService, we simply ignore the\n       * parallel tag and render it's contents like we normally would.\n       */\n      this.body.render(self, writer, context);\n\n    } else {\n\n      final EvaluationContextImpl contextCopy = context.threadSafeCopy(self);\n\n      final StringWriter newStringWriter = new StringWriter();\n      final Writer newFutureWriter = new FutureWriter(newStringWriter);\n\n      Future<String> future = es.submit(() -> {\n        this.body.render(self, newFutureWriter, contextCopy);\n        newFutureWriter.flush();\n        newFutureWriter.close();\n        return newStringWriter.toString();\n      });\n      ((FutureWriter) writer).enqueue(future);\n    }\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public BodyNode getBody() {\n    return this.body;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/PositionalArgumentNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\n\npublic class PositionalArgumentNode implements Node {\n\n  private final Expression<?> value;\n\n  public PositionalArgumentNode(Expression<?> value) {\n    this.value = value;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getValueExpression() {\n    return this.value;\n  }\n\n  @Override\n  public String toString() {\n    return this.value.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/PrintNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.extension.writer.SpecializedWriter;\nimport io.pebbletemplates.pebble.extension.writer.StringWriterSpecializedAdapter;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.StringUtils;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\n\npublic class PrintNode extends AbstractRenderableNode {\n\n  private Expression<?> expression;\n\n  public PrintNode(Expression<?> expression, int lineNumber) {\n    super(lineNumber);\n    this.expression = expression;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException,\n          PebbleException {\n    Object var = this.expression.evaluate(self, context);\n    if (var != null) {\n      if (writer instanceof StringWriter) {\n        new StringWriterSpecializedAdapter((StringWriter) writer).write(var);\n      } else if (writer instanceof SpecializedWriter) {\n        ((SpecializedWriter) writer).write(var);\n      } else {\n        writer.write(StringUtils.toString(var));\n      }\n    }\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getExpression() {\n    return this.expression;\n  }\n\n  public void setExpression(Expression<?> expression) {\n    this.expression = expression;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/RenderableNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\npublic interface RenderableNode extends Node {\n\n  void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException;\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/RootNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\npublic class RootNode extends AbstractRenderableNode {\n\n  private final BodyNode body;\n\n  public RootNode(BodyNode body) {\n    super(0);\n    this.body = body;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    this.body.setOnlyRenderInheritanceSafeNodes(true);\n    this.body.render(self, writer, context);\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public BodyNode getBody() {\n    return this.body;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/SetNode.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.Writer;\n\npublic class SetNode extends AbstractRenderableNode {\n\n  private final String name;\n\n  private final Expression<?> value;\n\n  public SetNode(int lineNumber, String name, Expression<?> value) {\n    super(lineNumber);\n    this.name = name;\n    this.value = value;\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context) {\n    context.getScopeChain().set(this.name, this.value.evaluate(self, context));\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getValue() {\n    return this.value;\n  }\n\n  public String getName() {\n    return this.name;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/TestInvocationExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\n/**\n * The right hand side to the test expression.\n *\n * @author Mitchell\n */\npublic class TestInvocationExpression implements Expression<Object> {\n\n  private final String testName;\n\n  private final ArgumentsNode args;\n\n  private final int lineNumber;\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    throw new UnsupportedOperationException();\n  }\n\n  public TestInvocationExpression(int lineNumber, String testName, ArgumentsNode args) {\n    this.testName = testName;\n    this.args = args;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public ArgumentsNode getArgs() {\n    return this.args;\n  }\n\n  public String getTestName() {\n    return this.testName;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/TextNode.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\n/**\n * Represents static text in a template.\n *\n * @author mbosecke\n */\npublic class TextNode extends AbstractRenderableNode {\n\n  /**\n   * Most Writers will convert strings to char[] so we might as well store it as a char[] to begin\n   * with; small performance optimization.\n   */\n  private final char[] data;\n\n  public TextNode(String text, int lineNumber) {\n    super(lineNumber);\n\n    int length = text.length();\n    this.data = new char[text.length()];\n    text.getChars(0, length, this.data, 0);\n  }\n\n  @Override\n  public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException {\n    writer.write(this.data);\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public char[] getData() {\n    return this.data;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/AddExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class AddExpression extends BinaryExpression<Object> {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n\n    try {\n      return OperatorUtils.add(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform addition\", this.getLineNumber(),\n          self.getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/AndExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport static io.pebbletemplates.pebble.utils.TypeUtils.compatibleCast;\n\npublic class AndExpression extends BinaryExpression<Boolean> {\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Expression<Boolean> leftExpression = (Expression<Boolean>) this.getLeftExpression();\n    boolean left = this.evaluateExpression(self, context, leftExpression);\n    if (left) {\n      Expression<Boolean> rightExpression = (Expression<Boolean>) this.getRightExpression();\n      return this.evaluateExpression(self, context, rightExpression);\n    }\n    return false;\n  }\n\n  private boolean evaluateExpression(PebbleTemplateImpl self, EvaluationContextImpl context,\n      Expression<Boolean> expression) {\n    Boolean evaluatedExpression = compatibleCast(expression.evaluate(self, context), Boolean.class);\n\n    if (evaluatedExpression == null) {\n      if (context.isStrictVariables()) {\n        throw new PebbleException(null,\n            \"null value used in and operator and strict variables is set to true\",\n            this.getLineNumber(),\n            self.getName());\n      }\n      return false;\n    }\n    return evaluatedExpression;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/ArrayExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class ArrayExpression implements Expression<List<?>> {\n\n  private final List<Expression<?>> values;\n  private final int lineNumber;\n\n  public ArrayExpression(int lineNumber) {\n    this.values = Collections.emptyList();\n    this.lineNumber = lineNumber;\n  }\n\n  public ArrayExpression(List<Expression<?>> values, int lineNumber) {\n    if (values == null) {\n      this.values = Collections.emptyList();\n    } else {\n      this.values = values;\n    }\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public List<?> evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    List<Object> returnValues = new ArrayList<>(this.values.size());\n    for (int i = 0; i < this.values.size(); i++) {\n      Expression<?> expr = this.values.get(i);\n      Object value = expr == null ? null : expr.evaluate(self, context);\n      returnValues.add(value);\n    }\n    return returnValues;\n  }\n\n  public List<Expression<?>> getValues() {\n    return this.values;\n  }\n  \n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/BinaryExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\n\npublic abstract class BinaryExpression<T> implements Expression<T> {\n\n  private int lineNumber;\n\n  public BinaryExpression() {\n\n  }\n\n  /**\n   * Sets the left and right expressions. This expression is assumed to be defined on the same line\n   * as the left expression.\n   */\n  public BinaryExpression(Expression<?> left, Expression<?> right) {\n    this.setLeft(left);\n    this.setRight(right);\n    this.setLineNumber(left.getLineNumber());\n  }\n\n  private Expression<?> leftExpression;\n\n  private Expression<?> rightExpression;\n\n  public void setLeft(Expression<?> left) {\n    this.leftExpression = left;\n  }\n\n  public void setRight(Expression<?> right) {\n    this.rightExpression = right;\n  }\n\n  public Expression<?> getLeftExpression() {\n    return this.leftExpression;\n  }\n\n  public Expression<?> getRightExpression() {\n    return this.rightExpression;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  /**\n   * Sets the line number on which the expression is defined on.\n   *\n   * @param lineNumber the line number on which the expression is defined on.\n   */\n  public void setLineNumber(int lineNumber) {\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/BlockFunctionExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\n\npublic class BlockFunctionExpression implements Expression<String> {\n\n  private final Expression<?> blockNameExpression;\n\n  private final int lineNumber;\n\n  public BlockFunctionExpression(ArgumentsNode args, int lineNumber) {\n    this.blockNameExpression = args.getPositionalArgs().get(0).getValueExpression();\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Writer writer = new StringWriter();\n    String blockName = (String) this.blockNameExpression.evaluate(self, context);\n    try {\n      self.block(writer, context, blockName, false);\n    } catch (IOException e) {\n      throw new PebbleException(e, \"Could not render block [\" + blockName + \"]\",\n          this.getLineNumber(), self.getName());\n    }\n    return writer.toString();\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getBlockNameExpression() {\n    return this.blockNameExpression;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/ConcatenateExpression.java",
    "content": "package io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\n/**\n * Expression which implements the string concatenation.\n *\n * @author Thomas Hunziker\n */\npublic class ConcatenateExpression extends BinaryExpression<Object> {\n\n  public ConcatenateExpression() {\n  }\n\n  public ConcatenateExpression(Expression<?> left, Expression<?> right) {\n    super(left, right);\n  }\n\n  @Override\n  public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n\n    Object left = getLeftExpression().evaluate(self, context);\n    Object right = getRightExpression().evaluate(self, context);\n    StringBuilder result = new StringBuilder();\n    if (left != null) {\n      result.append(left.toString());\n    }\n    if (right != null) {\n      result.append(right.toString());\n    }\n\n    return result.toString();\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"%s + %s\", getLeftExpression(), getRightExpression());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/ContainsExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.Collection;\nimport java.util.Map;\n\npublic class ContainsExpression extends BinaryExpression<Boolean> {\n\n  @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Object leftValue = this.getLeftExpression().evaluate(self, context);\n\n    if (leftValue == null) {\n      return false;\n    }\n    Object rightValue = this.getRightExpression().evaluate(self, context);\n\n    if (leftValue instanceof Collection) {\n      if (rightValue instanceof Collection) {\n        return ((Collection) leftValue).containsAll((Collection) rightValue);\n      } else {\n        return ((Collection) leftValue).contains(rightValue);\n      }\n    } else if (leftValue instanceof Map) {\n      return ((Map) leftValue).containsKey(rightValue);\n    } else if (leftValue.getClass().isArray()) {\n      return arrayContains(leftValue, rightValue);\n    } else if (leftValue instanceof String) {\n      return leftValue.toString().contains(String.valueOf(rightValue));\n    } else {\n      throw new PebbleException(null,\n          \"Contains operator can only be used on Collections, Maps and arrays. Actual type was: \"\n              + leftValue.getClass().getName(), this.getLineNumber(), self.getName());\n    }\n  }\n\n  // FIXME is this right? does it make sense to support?\n  private static boolean arrayContains(Object input, Object value) {\n    if (input instanceof Object[]) {\n      return containsObject((Object[]) input, value);\n    } else if (input instanceof boolean[]) {\n      return containsBoolean((boolean[]) input, value);\n    } else if (input instanceof byte[]) {\n      return containsByte((byte[]) input, value);\n    } else if (input instanceof char[]) {\n      return containsChar((char[]) input, value);\n    } else if (input instanceof double[]) {\n      return containsDouble((double[]) input, value);\n    } else if (input instanceof float[]) {\n      return containsFloat((float[]) input, value);\n    } else if (input instanceof int[]) {\n      return containsInt((int[]) input, value);\n    } else if (input instanceof long[]) {\n      return containsLong((long[]) input, value);\n    } else {\n      return containsShort((short[]) input, value);\n    }\n  }\n\n  private static boolean containsObject(Object[] array, Object value) {\n    for (Object o : array) {\n      if (value == o || (value != null && value.equals(o))) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsBoolean(boolean[] array, Object value) {\n    if (!(value instanceof Boolean)) {\n      return false;\n    }\n    for (boolean b : array) {\n      if (b == (Boolean) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsByte(byte[] array, Object value) {\n    if (!(value instanceof Byte)) {\n      return false;\n    }\n    for (byte b : array) {\n      if (b == (Byte) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsChar(char[] array, Object value) {\n    if (!(value instanceof Character)) {\n      return false;\n    }\n    for (char c : array) {\n      if (c == (Character) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsDouble(double[] array, Object value) {\n    if (!(value instanceof Double)) {\n      return false;\n    }\n    for (double d : array) {\n      if (d == (Double) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsFloat(float[] array, Object value) {\n    if (!(value instanceof Float)) {\n      return false;\n    }\n    for (float f : array) {\n      if (f == (Float) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsInt(int[] array, Object value) {\n    if (!(value instanceof Integer)) {\n      return false;\n    }\n    for (int i : array) {\n      if (i == (Integer) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsLong(long[] array, Object value) {\n    if (!(value instanceof Long)) {\n      return false;\n    }\n    for (long l : array) {\n      if (l == (Long) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private static boolean containsShort(short[] array, Object value) {\n    if (!(value instanceof Short)) {\n      return false;\n    }\n    for (short s : array) {\n      if (s == (Short) value) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/ContextVariableExpression.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.RootAttributeNotFoundException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.template.ScopeChain;\n\npublic class ContextVariableExpression implements Expression<Object> {\n\n  protected final String name;\n\n  private final int lineNumber;\n\n  public ContextVariableExpression(String name, int lineNumber) {\n    this.name = name;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public String getName() {\n    return this.name;\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    ScopeChain scopeChain = context.getScopeChain();\n    Object result = scopeChain.get(this.name);\n    if (result == null && context.isStrictVariables() && !scopeChain.containsKey(this.name)) {\n      throw new RootAttributeNotFoundException(null, String.format(\n          \"Root attribute [%s] does not exist or can not be accessed and strict variables is set to true.\",\n          this.name), this.name, this.lineNumber, self.getName());\n    }\n    return result;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"[%s]\", this.name);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/DivideExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class DivideExpression extends BinaryExpression<Object> {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return OperatorUtils.divide(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform division\", this.getLineNumber(),\n          self.getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/EqualsExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class EqualsExpression extends BinaryExpression<Boolean> {\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n\n    try {\n      return OperatorUtils.equals(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform equals comparison\", this.getLineNumber(),\n          self.getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/Expression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.node.Node;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic interface Expression<T> extends Node {\n\n  T evaluate(PebbleTemplateImpl self, EvaluationContextImpl context);\n\n  /**\n   * Returns the line number on which the expression is defined on.\n   *\n   * @return the line number on which the expression is defined on.\n   */\n  int getLineNumber();\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/FilterExpression.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.core.DefaultFilter;\nimport io.pebbletemplates.pebble.error.AttributeNotFoundException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.escaper.EscapeFilter;\nimport io.pebbletemplates.pebble.extension.escaper.SafeString;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.Map;\n\npublic class FilterExpression extends BinaryExpression<Object> {\n\n  /**\n   * Save the filter instance on the first evaluation.\n   */\n  private Filter filter = null;\n\n  public FilterExpression() {\n    super();\n\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n\n    FilterInvocationExpression filterInvocation = (FilterInvocationExpression) this\n        .getRightExpression();\n    ArgumentsNode args = filterInvocation.getArgs();\n    String filterName = filterInvocation.getFilterName();\n\n    if (this.filter == null) {\n      this.filter = context.getExtensionRegistry().getFilter(filterInvocation.getFilterName());\n    }\n\n    if (this.filter == null) {\n      throw new PebbleException(null, String.format(\"Filter [%s] does not exist.\", filterName),\n          this.getLineNumber(), self.getName());\n    }\n\n    Map<String, Object> namedArguments = args.getArgumentMap(self, context, this.filter);\n\n    // This check is not nice, because we use instanceof. However this is\n    // the only filter which should not fail in strict mode, when the variable\n    // is not set, because this method should exactly test this. Hence a\n    // generic solution to allow other tests to reuse this feature make no sense\n    Object input;\n    if (this.filter instanceof DefaultFilter) {\n      try {\n        input = this.getLeftExpression().evaluate(self, context);\n      } catch (AttributeNotFoundException ex) {\n        input = null;\n      }\n    } else {\n      input = this.getLeftExpression().evaluate(self, context);\n    }\n\n    if (input instanceof SafeString && !(this.filter instanceof EscapeFilter)) {\n      input = input.toString();\n    }\n\n    return this.filter.apply(input, namedArguments, self, context, this.getLineNumber());\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/FilterInvocationExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\n/**\n * The right hand side to the filter expression.\n *\n * @author Mitchell\n */\npublic class FilterInvocationExpression implements Expression<Object> {\n\n  private final String filterName;\n\n  private final ArgumentsNode args;\n\n  private final int lineNumber;\n\n  public FilterInvocationExpression(String filterName, ArgumentsNode args, int lineNumber) {\n    this.filterName = filterName;\n    this.args = args;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    // see FilterExpression.java\n    throw new UnsupportedOperationException();\n  }\n\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public ArgumentsNode getArgs() {\n    return this.args;\n  }\n\n  public String getFilterName() {\n    return this.filterName;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/FunctionOrMacroInvocationExpression.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.Map;\n\npublic class FunctionOrMacroInvocationExpression implements Expression<Object> {\n\n  private final String functionName;\n\n  private final ArgumentsNode args;\n\n  private final int lineNumber;\n\n  public FunctionOrMacroInvocationExpression(String functionName, ArgumentsNode arguments,\n      int lineNumber) {\n    this.functionName = functionName;\n    this.args = arguments;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Function function = context.getExtensionRegistry().getFunction(this.functionName);\n    if (function != null) {\n      return this.applyFunction(self, context, function, this.args);\n    }\n    return self.macro(context, this.functionName, this.args, false, this.lineNumber);\n  }\n\n  private Object applyFunction(PebbleTemplateImpl self, EvaluationContextImpl context,\n      Function function, ArgumentsNode args) {\n    Map<String, Object> namedArguments = args.getArgumentMap(self, context, function);\n    return function.execute(namedArguments, self, context, this.getLineNumber());\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public String getFunctionName() {\n    return this.functionName;\n  }\n\n  public ArgumentsNode getArguments() {\n    return this.args;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"%s%s\", this.functionName, this.args);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/GetAttributeExpression.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.AttributeNotFoundException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.error.RootAttributeNotFoundException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.attributes.AttributeResolver;\nimport io.pebbletemplates.pebble.attributes.ResolvedAttribute;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.PositionalArgumentNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.List;\n\n/**\n * Used to get an attribute from an object. It will look up attributes in the following order: map\n * entry, array item, list item, get method, is method, has method, public method, public field.\n *\n * @author Mitchell\n */\npublic class GetAttributeExpression implements Expression<Object> {\n\n  private final Expression<?> node;\n\n  private final Expression<?> attributeNameExpression;\n\n  private final ArgumentsNode args;\n\n  private final String filename;\n\n  private final int lineNumber;\n\n  public GetAttributeExpression(Expression<?> node, Expression<?> attributeNameExpression,\n      String filename,\n      int lineNumber) {\n    this(node, attributeNameExpression, null, filename, lineNumber);\n  }\n\n  public GetAttributeExpression(Expression<?> node, Expression<?> attributeNameExpression,\n      ArgumentsNode args,\n      String filename, int lineNumber) {\n\n    this.node = node;\n    this.attributeNameExpression = attributeNameExpression;\n    this.args = args;\n    this.filename = filename;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context)\n      throws PebbleException {\n    final Object object = this.node.evaluate(self, context);\n    final Object attributeNameValue = this.attributeNameExpression.evaluate(self, context);\n    final String attributeName = String.valueOf(attributeNameValue);\n    final Object[] argumentValues = this.getArgumentValues(self, context);\n\n    if (object == null && context.isStrictVariables()) {\n      if (this.node instanceof ContextVariableExpression) {\n        final String rootPropertyName = ((ContextVariableExpression) this.node).getName();\n        throw new RootAttributeNotFoundException(null, String.format(\n            \"Root attribute [%s] does not exist or can not be accessed and strict variables is set to true.\",\n            rootPropertyName), rootPropertyName, this.lineNumber, this.filename);\n      } else {\n        throw new RootAttributeNotFoundException(null,\n            \"Attempt to get attribute of null object and strict variables is set to true.\",\n            attributeName, this.lineNumber, this.filename);\n      }\n    }\n\n    for (AttributeResolver attributeResolver : context.getExtensionRegistry()\n        .getAttributeResolver()) {\n      ResolvedAttribute resolvedAttribute = attributeResolver\n          .resolve(object, attributeNameValue, argumentValues, this.args, context, this.filename,\n              this.lineNumber);\n      if (resolvedAttribute != null) {\n        return resolvedAttribute.evaluatedValue;\n      }\n    }\n\n    if (context.isStrictVariables()) {\n      throw new AttributeNotFoundException(null, String.format(\n          \"Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.\",\n          attributeName,\n          object != null ? object.getClass().getName() : null),\n          attributeName,\n          this.lineNumber,\n          this.filename);\n    }\n    return null;\n  }\n\n  /**\n   * Fully evaluates the individual arguments.\n   */\n  private Object[] getArgumentValues(PebbleTemplateImpl self, EvaluationContextImpl context) {\n\n    Object[] argumentValues;\n\n    if (this.args == null) {\n      argumentValues = null;\n    } else {\n      List<PositionalArgumentNode> args = this.args.getPositionalArgs();\n\n      argumentValues = new Object[args.size()];\n\n      int index = 0;\n      for (PositionalArgumentNode arg : args) {\n        Object argumentValue = arg.getValueExpression().evaluate(self, context);\n        argumentValues[index] = argumentValue;\n        index++;\n      }\n    }\n    return argumentValues;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getNode() {\n    return this.node;\n  }\n\n  public Expression<?> getAttributeNameExpression() {\n    return this.attributeNameExpression;\n  }\n\n  public ArgumentsNode getArgumentsNode() {\n    return this.args;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/GreaterThanEqualsExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class GreaterThanEqualsExpression extends BinaryExpression<Boolean> {\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n\n    try {\n      return OperatorUtils.gte(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform greater than or equals comparison\",\n          this.getLineNumber(), self\n          .getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/GreaterThanExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class GreaterThanExpression extends BinaryExpression<Boolean> {\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return OperatorUtils.gt(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform greater than comparison\",\n          this.getLineNumber(),\n          self.getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LessThanEqualsExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class LessThanEqualsExpression extends BinaryExpression<Boolean> {\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return OperatorUtils.lte(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform less than or equals comparison\",\n          this.getLineNumber(), self\n          .getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LessThanExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class LessThanExpression extends BinaryExpression<Boolean> {\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return OperatorUtils.lt(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform greater modulus\", this.getLineNumber(), self\n          .getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LiteralBigDecimalExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.math.BigDecimal;\n\npublic class LiteralBigDecimalExpression implements Expression<BigDecimal> {\n\n  private final BigDecimal value;\n  private final int lineNumber;\n\n  public LiteralBigDecimalExpression(BigDecimal value, int lineNumber) {\n    this.value = value;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public BigDecimal evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return this.value;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public BigDecimal getValue() {\n    return this.value;\n  }\n\n  @Override\n  public String toString() {\n    return this.value.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LiteralBooleanExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class LiteralBooleanExpression implements Expression<Boolean> {\n\n  private final Boolean value;\n\n  private final int lineNumber;\n\n  public LiteralBooleanExpression(Boolean value, int lineNumber) {\n    this.value = value;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return this.value;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public Boolean getValue() {\n    return this.value;\n  }\n\n  @Override\n  public String toString() {\n    return this.value.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LiteralDoubleExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class LiteralDoubleExpression implements Expression<Double> {\n\n  private final Double value;\n\n  private final int lineNumber;\n\n  public LiteralDoubleExpression(Double value, int lineNumber) {\n    this.value = value;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public Double evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return this.value;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public Double getValue() {\n    return this.value;\n  }\n\n  @Override\n  public String toString() {\n    return this.value.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LiteralIntegerExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class LiteralIntegerExpression implements Expression<Integer> {\n\n  private final Integer value;\n  private final int lineNumber;\n\n  public LiteralIntegerExpression(Integer value, int lineNumber) {\n    this.value = value;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public Integer evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return this.value;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public Integer getValue() {\n    return this.value;\n  }\n\n  @Override\n  public String toString() {\n    return this.value.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LiteralLongExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class LiteralLongExpression implements Expression<Long> {\n\n  private final Long value;\n  private final int lineNumber;\n\n  public LiteralLongExpression(Long value, int lineNumber) {\n    this.value = value;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public Long evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return this.value;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public Long getValue() {\n    return this.value;\n  }\n\n  @Override\n  public String toString() {\n    return this.value.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LiteralNullExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class LiteralNullExpression implements Expression<Object> {\n\n  private final int lineNumber;\n\n  public LiteralNullExpression(int lineNumber) {\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return null;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/LiteralStringExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class LiteralStringExpression implements Expression<String> {\n\n  private final String value;\n\n  private final int lineNumber;\n\n  public LiteralStringExpression(String value, int lineNumber) {\n    this.value = value;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return this.value;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n  public String getValue() {\n    return this.value;\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"\\\"%s\\\"\", this.value);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/MapExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\npublic class MapExpression implements Expression<Map<?, ?>> {\n\n  // FIXME should keys be of any type?\n  private final Map<Expression<?>, Expression<?>> entries;\n  private final int lineNumber;\n\n  public MapExpression(int lineNumber) {\n    this.entries = Collections.emptyMap();\n    this.lineNumber = lineNumber;\n  }\n\n  public MapExpression(Map<Expression<?>, Expression<?>> entries, int lineNumber) {\n    if (entries == null) {\n      this.entries = Collections.emptyMap();\n    } else {\n      this.entries = entries;\n    }\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  @Override\n  public Map<?, ?> evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Map<Object, Object> returnEntries = new HashMap<>(\n        Long.valueOf(Math.round(Math.ceil(this.entries.size() / 0.75)))\n            .intValue());\n    for (Entry<Expression<?>, Expression<?>> entry: this.entries.entrySet()) {\n      Expression<?> keyExpr = entry.getKey();\n      Expression<?> valueExpr = entry.getValue();\n      Object key = keyExpr == null ? null : keyExpr.evaluate(self, context);\n      Object value = valueExpr == null ? null : valueExpr.evaluate(self, context);\n      returnEntries.put(key, value);\n    }\n    return returnEntries;\n  }\n\n  public Map<Expression<?>, Expression<?>> getEntries() {\n    return this.entries;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/ModulusExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class ModulusExpression extends BinaryExpression<Object> {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return OperatorUtils.mod(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform greater modulus\", this.getLineNumber(), self\n          .getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/MultiplyExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class MultiplyExpression extends BinaryExpression<Object> {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return OperatorUtils.multiply(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform multiplication\", this.getLineNumber(), self\n          .getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/NegativeTestExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\npublic class NegativeTestExpression extends PositiveTestExpression {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return !((Boolean) super.evaluate(self, context));\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/NotEqualsExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class NotEqualsExpression extends BinaryExpression<Boolean> {\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return !OperatorUtils\n          .equals(this.getLeftExpression().evaluate(self, context),\n              this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform not equals comparison\", this.getLineNumber(),\n          self.getName());\n    }\n\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/OrExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport static io.pebbletemplates.pebble.utils.TypeUtils.compatibleCast;\n\npublic class OrExpression extends BinaryExpression<Boolean> {\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Expression<Boolean> leftExpression = (Expression<Boolean>) this.getLeftExpression();\n    boolean left = this.evaluateExpression(self, context, leftExpression);\n    if (!left) {\n      Expression<Boolean> rightExpression = (Expression<Boolean>) this.getRightExpression();\n      return this.evaluateExpression(self, context, rightExpression);\n    }\n    return true;\n  }\n\n  private boolean evaluateExpression(PebbleTemplateImpl self, EvaluationContextImpl context,\n      Expression<Boolean> expression) {\n    Boolean evaluatedExpression = compatibleCast(expression.evaluate(self, context), Boolean.class);\n\n    if (evaluatedExpression == null) {\n      if (context.isStrictVariables()) {\n        throw new PebbleException(null,\n            \"null value used in and operator and strict variables is set to true\",\n            this.getLineNumber(),\n            self.getName());\n      }\n      return false;\n    }\n    return evaluatedExpression;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/ParentFunctionExpression.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.Hierarchy;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\n\npublic class ParentFunctionExpression implements Expression<String> {\n\n  private final String blockName;\n\n  private final int lineNumber;\n\n  public ParentFunctionExpression(String blockName, int lineNumber) {\n    this.blockName = blockName;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Writer writer = new StringWriter();\n    try {\n      Hierarchy hierarchy = context.getHierarchy();\n      if (hierarchy.getParent() == null) {\n        throw new PebbleException(null,\n            \"Can not use parent function if template does not extend another template.\",\n            this.lineNumber,\n            self.getName());\n      }\n      PebbleTemplateImpl parent = hierarchy.getParent();\n\n      hierarchy.ascend();\n      parent.block(writer, context, this.blockName, true);\n      hierarchy.descend();\n    } catch (IOException e) {\n      throw new PebbleException(e, \"Could not render block [\" + this.blockName + \"]\",\n          this.getLineNumber(),\n          self.getName());\n    }\n    return writer.toString();\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public String getBlockName() {\n    return this.blockName;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/PositiveTestExpression.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.AttributeNotFoundException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.Test;\nimport io.pebbletemplates.pebble.extension.core.DefinedTest;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.TestInvocationExpression;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.Map;\n\npublic class PositiveTestExpression extends BinaryExpression<Object> {\n\n  private Test cachedTest;\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n\n    TestInvocationExpression testInvocation = (TestInvocationExpression) this.getRightExpression();\n    ArgumentsNode args = testInvocation.getArgs();\n\n    if (this.cachedTest == null) {\n      String testName = testInvocation.getTestName();\n\n      this.cachedTest = context.getExtensionRegistry().getTest(testInvocation.getTestName());\n\n      if (this.cachedTest == null) {\n        throw new PebbleException(null, String.format(\"Test [%s] does not exist.\", testName),\n            this.getLineNumber(), self.getName());\n      }\n    }\n    Test test = this.cachedTest;\n\n    Map<String, Object> namedArguments = args.getArgumentMap(self, context, test);\n\n    // This check is not nice, because we use instanceof. However this is\n    // the only test which should not fail in strict mode, when the variable\n    // is not set, because this method should exactly test this. Hence a\n    // generic solution to allow other tests to reuse this feature make no\n    // sense.\n    if (test instanceof DefinedTest) {\n      Object input = null;\n      try {\n        input = this.getLeftExpression().evaluate(self, context);\n      } catch (AttributeNotFoundException e) {\n        input = null;\n      }\n      return test.apply(input, namedArguments, self, context, this.getLineNumber());\n    } else {\n      return test\n          .apply(this.getLeftExpression().evaluate(self, context), namedArguments, self, context,\n              this.getLineNumber());\n    }\n\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/RangeExpression.java",
    "content": "package io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.core.RangeFunction;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.PositionalArgumentNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Expression which implements the range function.\n *\n * @author Eric Bussieres\n */\npublic class RangeExpression extends BinaryExpression<Object> {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    List<PositionalArgumentNode> positionalArgs = new ArrayList<>();\n    positionalArgs.add(new PositionalArgumentNode(getLeftExpression()));\n    positionalArgs.add(new PositionalArgumentNode(getRightExpression()));\n\n    ArgumentsNode arguments = new ArgumentsNode(positionalArgs, null, this.getLineNumber());\n    FunctionOrMacroInvocationExpression function = new FunctionOrMacroInvocationExpression(\n        RangeFunction.FUNCTION_NAME, arguments, this.getLineNumber());\n\n    return function.evaluate(self, context);\n  }\n\n}"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/RenderableNodeExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.LimitedSizeWriter;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\n\n/**\n * This class wraps a {@link RenderableNode} into an expression. This is used by the filter TAG to\n * apply a filter to large chunk of template which is contained within a renderable node.\n *\n * @author mbosecke\n */\npublic class RenderableNodeExpression extends UnaryExpression {\n\n  private final RenderableNode node;\n\n  private final int lineNumber;\n\n  public RenderableNodeExpression(RenderableNode node, int lineNumber) {\n    this.node = node;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Writer writer = LimitedSizeWriter.from(new StringWriter(), context);\n    try {\n      this.node.render(self, writer, context);\n    } catch (IOException e) {\n      throw new PebbleException(e, \"Error occurred while rendering node\", this.getLineNumber(),\n          self.getName());\n    }\n    return writer.toString();\n  }\n\n  public RenderableNode getNode() {\n    return this.node;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/SubtractExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class SubtractExpression extends BinaryExpression<Object> {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    try {\n      return OperatorUtils.subtract(this.getLeftExpression().evaluate(self, context),\n          this.getRightExpression().evaluate(self, context));\n    } catch (Exception ex) {\n      throw new PebbleException(ex, \"Could not perform subtraction\", this.getLineNumber(),\n          self.getName());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/TernaryExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.TypeUtils;\n\nimport static io.pebbletemplates.pebble.utils.TypeUtils.compatibleCast;\n\npublic class TernaryExpression implements Expression<Object> {\n\n  private final Expression<Boolean> expression1;\n\n  private Expression<?> expression2;\n\n  private Expression<?> expression3;\n\n  private final int lineNumber;\n\n  public TernaryExpression(Expression<Boolean> expression1, Expression<?> expression2,\n      Expression<?> expression3, int lineNumber, String filename) {\n    this.expression1 = expression1;\n    this.expression2 = expression2;\n    this.expression3 = expression3;\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Object evaluatedExpression1 = this.expression1.evaluate(self, context);\n    if (evaluatedExpression1 != null\n            && TypeUtils.compatibleCast(evaluatedExpression1, Boolean.class)) {\n      return this.expression2.evaluate(self, context);\n    } else {\n      return this.expression3.evaluate(self, context);\n    }\n  }\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<Boolean> getExpression1() {\n    return this.expression1;\n  }\n\n  public Expression<?> getExpression2() {\n    return this.expression2;\n  }\n\n  public Expression<?> getExpression3() {\n    return this.expression3;\n  }\n\n  public void setExpression3(Expression<?> expression3) {\n    this.expression3 = expression3;\n  }\n\n  public void setExpression2(Expression<?> expression2) {\n    this.expression2 = expression2;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/UnaryExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\n\npublic abstract class UnaryExpression implements Expression<Object> {\n\n  private Expression<?> childExpression;\n\n  private int lineNumber;\n\n  @Override\n  public void accept(NodeVisitor visitor) {\n    visitor.visit(this);\n  }\n\n  public Expression<?> getChildExpression() {\n    return this.childExpression;\n  }\n\n  public void setChildExpression(Expression<?> childExpression) {\n    this.childExpression = childExpression;\n  }\n\n\n  /**\n   * Sets the line number on which the expression is defined on.\n   *\n   * @param lineNumber the line number on which the expression is defined on.\n   */\n  public void setLineNumber(int lineNumber) {\n    this.lineNumber = lineNumber;\n  }\n\n  @Override\n  public int getLineNumber() {\n    return this.lineNumber;\n  }\n\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/UnaryMinusExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class UnaryMinusExpression extends UnaryExpression {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return OperatorUtils.unaryMinus(this.getChildExpression().evaluate(self, context));\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/UnaryNotExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.TypeUtils;\n\nimport static io.pebbletemplates.pebble.utils.TypeUtils.compatibleCast;\n\npublic class UnaryNotExpression extends UnaryExpression {\n\n  @Override\n  public Boolean evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    Object result = this.getChildExpression().evaluate(self, context);\n    if (result != null) {\n      if (result instanceof Boolean\n              || result instanceof Number\n              || result instanceof String) {\n        return !TypeUtils.compatibleCast(result, Boolean.class);\n      }\n      throw new PebbleException(\n              null,\n              String.format(\n                      \"Unsupported value type %s. Expected Boolean, String, Number in \\\"if\\\" statement\",\n                      result.getClass().getSimpleName()),\n              this.getLineNumber(),\n              self.getName());\n\n    }\n    // input is null\n    if (context.isStrictVariables()) {\n      throw new PebbleException(null,\n              \"null value given to not() and strict variables is set to true\", this.getLineNumber(),\n              self.getName());\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/expression/UnaryPlusExpression.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.utils.OperatorUtils;\n\npublic class UnaryPlusExpression extends UnaryExpression {\n\n  @Override\n  public Object evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n    return OperatorUtils.unaryPlus(this.getChildExpression().evaluate(self, context));\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/fornode/LazyLength.java",
    "content": "package io.pebbletemplates.pebble.node.fornode;\n\nimport java.lang.reflect.Array;\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.Iterator;\nimport java.util.Map;\n\npublic class LazyLength extends Number {\n\n  private final Object iterableEvaluation;\n  private int value = -1;\n\n  public LazyLength(Object iterableEvaluation) {\n    this.iterableEvaluation = iterableEvaluation;\n  }\n\n  @Override\n  public int intValue() {\n    return this.getValue();\n  }\n\n  @Override\n  public long longValue() {\n    return (long) this.getValue();\n  }\n\n  @Override\n  public float floatValue() {\n    return (float) this.getValue();\n  }\n\n  @Override\n  public double doubleValue() {\n    return (double) this.getValue();\n  }\n\n  @Override\n  public String toString() {\n    return String.valueOf(this.getValue());\n  }\n\n  private int getValue() {\n    if (this.value == -1) {\n      this.value = this.getIteratorSize(this.iterableEvaluation);\n    }\n    return this.value;\n  }\n\n  private int getIteratorSize(Object iterable) {\n    if (iterable == null) {\n      return 0;\n    }\n    if (iterable instanceof Collection) {\n      return ((Collection<?>) iterable).size();\n    } else if (iterable instanceof Map) {\n      return ((Map<?, ?>) iterable).size();\n    } else if (iterable.getClass().isArray()) {\n      return Array.getLength(iterable);\n    } else if (iterable instanceof Enumeration) {\n      Enumeration<?> enumeration = (Enumeration<?>) iterable;\n      int size = 0;\n      while (enumeration.hasMoreElements()) {\n        size++;\n        enumeration.nextElement();\n      }\n      return size;\n    }\n\n    // assumed to be of type Iterator\n    Iterator<?> it = ((Iterable<?>) iterable).iterator();\n    int size = 0;\n    while (it.hasNext()) {\n      size++;\n      it.next();\n    }\n    return size;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/node/fornode/LazyRevIndex.java",
    "content": "package io.pebbletemplates.pebble.node.fornode;\n\npublic class LazyRevIndex extends Number {\n  private final int value;\n  private final LazyLength lazyLength;\n\n  public LazyRevIndex(int value, LazyLength lazyLength) {\n    this.value = value;\n    this.lazyLength = lazyLength;\n  }\n\n  @Override\n  public int intValue() {\n    return this.getValue();\n  }\n\n  @Override\n  public long longValue() {\n    return (long) this.getValue();\n  }\n\n  @Override\n  public float floatValue() {\n    return (float) this.getValue();\n  }\n\n  @Override\n  public double doubleValue() {\n    return (double) this.getValue();\n  }\n\n  @Override\n  public String toString() {\n    return String.valueOf(this.getValue());\n  }\n\n  private int getValue() {\n    return this.lazyLength.intValue() - this.value - 1;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/operator/Associativity.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.operator;\n\npublic enum Associativity {\n  LEFT, RIGHT\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/operator/BinaryOperator.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.operator;\n\nimport io.pebbletemplates.pebble.node.expression.BinaryExpression;\n\npublic interface BinaryOperator {\n\n  int getPrecedence();\n\n  String getSymbol();\n\n  BinaryExpression<?> createInstance();\n\n  BinaryOperatorType getType();\n\n  Associativity getAssociativity();\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/operator/BinaryOperatorImpl.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.operator;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.node.expression.BinaryExpression;\nimport io.pebbletemplates.pebble.node.expression.FilterExpression;\nimport io.pebbletemplates.pebble.node.expression.NegativeTestExpression;\nimport io.pebbletemplates.pebble.node.expression.PositiveTestExpression;\nimport java.util.function.Supplier;\n\npublic class BinaryOperatorImpl implements BinaryOperator {\n\n  private final int precedence;\n\n  private final String symbol;\n\n  private final Supplier<? extends BinaryExpression<?>> nodeSupplier;\n\n  private final BinaryOperatorType type;\n\n  private final Associativity associativity;\n\n  /**\n   * This constuctor left for backward compatibility with custom extensions\n   */\n  public BinaryOperatorImpl(String symbol, int precedence,\n      Class<? extends BinaryExpression<?>> nodeClass,\n      Associativity associativity) {\n    this(symbol, precedence, () -> {\n      try {\n        return nodeClass.newInstance();\n      } catch (InstantiationException | IllegalAccessException e) {\n        throw new PebbleException(e, \"Error instantiating class \" + nodeClass.getName());\n      }\n    }, getDefaultType(nodeClass), associativity);\n  }\n\n  /**\n   * This constuctor allows you to completely control the instantiation of the expression class\n   */\n  public BinaryOperatorImpl(String symbol, int precedence,\n      Supplier<? extends BinaryExpression<?>> nodeSupplier,\n      BinaryOperatorType type,\n      Associativity associativity) {\n    this.symbol = symbol;\n    this.precedence = precedence;\n    this.nodeSupplier = nodeSupplier;\n    this.type = type;\n    this.associativity = associativity;\n  }\n\n  @Override\n  public int getPrecedence() {\n    return this.precedence;\n  }\n\n  @Override\n  public String getSymbol() {\n    return this.symbol;\n  }\n\n  @Override\n  public BinaryExpression<?> createInstance() {\n    return this.nodeSupplier.get();\n  }\n\n  @Override\n  public BinaryOperatorType getType() {\n    return this.type;\n  }\n\n  @Override\n  public Associativity getAssociativity() {\n    return this.associativity;\n  }\n\n  private static BinaryOperatorType getDefaultType(Class<? extends BinaryExpression<?>> nodeClass) {\n    if (FilterExpression.class.equals(nodeClass)) {\n      return BinaryOperatorType.FILTER;\n    } else if (PositiveTestExpression.class.equals(nodeClass) || NegativeTestExpression.class\n        .equals(nodeClass)) {\n      return BinaryOperatorType.TEST;\n    } else {\n      return BinaryOperatorType.NORMAL;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/operator/BinaryOperatorType.java",
    "content": "package io.pebbletemplates.pebble.operator;\n\npublic enum BinaryOperatorType {\n  NORMAL, FILTER, TEST\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/operator/UnaryOperator.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.operator;\n\nimport io.pebbletemplates.pebble.node.expression.UnaryExpression;\n\npublic interface UnaryOperator {\n\n  int getPrecedence();\n\n  String getSymbol();\n\n  Class<? extends UnaryExpression> getNodeClass();\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/operator/UnaryOperatorImpl.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.operator;\n\nimport io.pebbletemplates.pebble.node.expression.UnaryExpression;\n\npublic class UnaryOperatorImpl implements UnaryOperator {\n\n  private final int precedence;\n\n  private final String symbol;\n\n  private final Class<? extends UnaryExpression> nodeClass;\n\n  public UnaryOperatorImpl(String symbol, int precedence,\n      Class<? extends UnaryExpression> nodeClass) {\n    this.symbol = symbol;\n    this.precedence = precedence;\n    this.nodeClass = nodeClass;\n  }\n\n  @Override\n  public int getPrecedence() {\n    return this.precedence;\n  }\n\n  @Override\n  public String getSymbol() {\n    return this.symbol;\n  }\n\n  @Override\n  public Class<? extends UnaryExpression> getNodeClass() {\n    return this.nodeClass;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/parser/ExpressionParser.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.parser;\n\nimport io.pebbletemplates.pebble.operator.Associativity;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.BinaryOperatorType;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.FunctionOrMacroNameNode;\nimport io.pebbletemplates.pebble.node.NamedArgumentNode;\nimport io.pebbletemplates.pebble.node.PositionalArgumentNode;\nimport io.pebbletemplates.pebble.node.TestInvocationExpression;\nimport io.pebbletemplates.pebble.node.expression.ArrayExpression;\nimport io.pebbletemplates.pebble.node.expression.BinaryExpression;\nimport io.pebbletemplates.pebble.node.expression.BlockFunctionExpression;\nimport io.pebbletemplates.pebble.node.expression.ConcatenateExpression;\nimport io.pebbletemplates.pebble.node.expression.ContextVariableExpression;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.expression.FilterInvocationExpression;\nimport io.pebbletemplates.pebble.node.expression.FunctionOrMacroInvocationExpression;\nimport io.pebbletemplates.pebble.node.expression.GetAttributeExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralBigDecimalExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralBooleanExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralDoubleExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralIntegerExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralLongExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralNullExpression;\nimport io.pebbletemplates.pebble.node.expression.LiteralStringExpression;\nimport io.pebbletemplates.pebble.node.expression.MapExpression;\nimport io.pebbletemplates.pebble.node.expression.ParentFunctionExpression;\nimport io.pebbletemplates.pebble.node.expression.TernaryExpression;\nimport io.pebbletemplates.pebble.node.expression.UnaryExpression;\n\nimport java.math.BigDecimal;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Parses expressions.\n */\npublic class ExpressionParser {\n\n  private static final Set<String> RESERVED_KEYWORDS = new HashSet<>(\n      Arrays.asList(\"true\", \"false\", \"null\", \"none\"));\n\n  private final Parser parser;\n\n  private TokenStream stream;\n\n  private Map<String, BinaryOperator> binaryOperators;\n\n  private Map<String, UnaryOperator> unaryOperators;\n\n  private ParserOptions parserOptions;\n\n  /**\n   * Constructor\n   *\n   * @param parser A reference to the main parser\n   * @param binaryOperators All the binary operators\n   * @param unaryOperators All the unary operators\n   */\n  public ExpressionParser(Parser parser, Map<String, BinaryOperator> binaryOperators,\n      Map<String, UnaryOperator> unaryOperators, ParserOptions parserOptions) {\n    this.parser = parser;\n    this.binaryOperators = binaryOperators;\n    this.unaryOperators = unaryOperators;\n    this.parserOptions = parserOptions;\n  }\n\n  /**\n   * The public entry point for parsing an expression.\n   *\n   * @return NodeExpression the expression that has been parsed.\n   */\n  public Expression<?> parseExpression() {\n    return this.parseExpression(0);\n  }\n\n  /**\n   * A private entry point for parsing an expression. This method takes in the precedence required\n   * to operate a \"precedence climbing\" parsing algorithm. It is a recursive method.\n   *\n   * @return The NodeExpression representing the parsed expression.\n   * @see \"http://en.wikipedia.org/wiki/Operator-precedence_parser\"\n   */\n  private Expression<?> parseExpression(int minPrecedence) {\n\n    this.stream = this.parser.getStream();\n    Token token = this.stream.current();\n    Expression<?> expression;\n\n    /*\n     * The first check is to see if the expression begins with a unary\n     * operator, or an opening bracket, or neither.\n     */\n    if (this.isUnary(token)) {\n      UnaryOperator operator = this.unaryOperators.get(token.getValue());\n      this.stream.next();\n      expression = this.parseExpression(operator.getPrecedence());\n\n      UnaryExpression unaryExpression;\n      Class<? extends UnaryExpression> operatorNodeClass = operator.getNodeClass();\n      try {\n        unaryExpression = operatorNodeClass.newInstance();\n        unaryExpression.setLineNumber(this.stream.current().getLineNumber());\n      } catch (InstantiationException | IllegalAccessException e) {\n        throw new RuntimeException(e);\n      }\n      unaryExpression.setChildExpression(expression);\n\n      expression = unaryExpression;\n\n    } else if (token.test(Token.Type.PUNCTUATION, \"(\")) {\n\n      this.stream.next();\n      expression = this.parseExpression();\n      this.stream.expect(Token.Type.PUNCTUATION, \")\");\n      expression = this.parsePostfixExpression(expression);\n\n    }\n    // array definition syntax\n    else if (token.test(Token.Type.PUNCTUATION, \"[\")) {\n\n      // preserve [ token for array parsing\n      expression = this.parseArrayDefinitionExpression();\n      // don't expect ], because it has been already expected\n      // currently, postfix expressions are not supported for arrays\n      // expression = parsePostfixExpression(expression);\n    }\n    // map definition syntax\n    else if (token.test(Token.Type.PUNCTUATION, \"{\")) {\n\n      // preserve { token for map parsing\n      expression = this.parseMapDefinitionExpression();\n      // don't expect }, because it has been already expected\n      // currently, postfix expressions are not supported for maps\n      // expression = parsePostfixExpression(expression);\n\n    } else {\n      /*\n       * starts with neither. Let's parse out the first expression that we\n       * can find. There may be one, there may be many (separated by\n       * binary operators); right now we are just looking for the first.\n       */\n      expression = this.subparseExpression();\n    }\n\n    /*\n     * If, after parsing the first expression we encounter a binary operator\n     * then we know we have another expression on the other side of the\n     * operator that requires parsing. Otherwise we're done.\n     */\n    token = this.stream.current();\n    while (this.isBinary(token)\n        && this.binaryOperators.get(token.getValue()).getPrecedence() >= minPrecedence) {\n\n      // find out which operator we are dealing with and then skip over it\n      BinaryOperator operator = this.binaryOperators.get(token.getValue());\n      this.stream.next();\n\n      Expression<?> expressionRight;\n\n      // the right hand expression of the FILTER operator is handled in a\n      // unique way\n      if (operator.getType() == BinaryOperatorType.FILTER) {\n        expressionRight = this.parseFilterInvocationExpression();\n      }\n      // the right hand expression of TEST operators is handled in a\n      // unique way\n      else if (operator.getType() == BinaryOperatorType.TEST) {\n        expressionRight = this.parseTestInvocationExpression();\n      } else {\n        /*\n         * parse the expression on the right hand side of the operator\n         * while maintaining proper associativity and precedence\n         */\n        expressionRight = this.parseExpression(\n            Associativity.LEFT.equals(operator.getAssociativity()) ? operator\n                .getPrecedence() + 1 : operator.getPrecedence());\n      }\n\n      /*\n       * we have to wrap the left and right side expressions into one\n       * final expression. The operator provides us with the type of\n       * expression we are creating.\n       */\n      BinaryExpression<?> finalExpression;\n\n      try {\n        finalExpression = operator.createInstance();\n      } catch (RuntimeException e) {\n        throw new ParserException(e,\n            \"Error instantiating operator node\",\n            token.getLineNumber(), this.stream.getFilename());\n      }\n\n      finalExpression.setLineNumber(this.stream.current().getLineNumber());\n      finalExpression.setLeft(expression);\n      finalExpression.setRight(expressionRight);\n\n      expression = finalExpression;\n\n      token = this.stream.current();\n    }\n\n    if (minPrecedence == 0) {\n      return this.parseTernaryExpression(expression);\n    }\n\n    return expression;\n  }\n\n  /**\n   * Checks if a token is a unary operator.\n   *\n   * @param token The token that we are checking\n   * @return boolean Whether the token is a unary operator or not\n   */\n  private boolean isUnary(Token token) {\n    return token.test(Token.Type.OPERATOR) && this.unaryOperators.containsKey(token.getValue());\n  }\n\n  /**\n   * Checks if a token is a binary operator.\n   *\n   * @param token The token that we are checking\n   * @return boolean Whether the token is a binary operator or not\n   */\n  private boolean isBinary(Token token) {\n    return token.test(Token.Type.OPERATOR) && this.binaryOperators.containsKey(token.getValue());\n  }\n\n  /**\n   * Finds and returns the next \"simple\" expression; an expression of which can be found on either\n   * side of a binary operator but does not contain a binary operator. Ex. \"var.field\", \"true\",\n   * \"12\", etc.\n   *\n   * @return NodeExpression The expression that it found.\n   */\n  private Expression<?> subparseExpression() {\n    final Token token = this.stream.current();\n    Expression<?> node;\n\n    switch (token.getType()) {\n\n      case NAME:\n        switch (token.getValue()) {\n\n          // a constant?\n          case \"true\":\n          case \"TRUE\":\n            node = new LiteralBooleanExpression(true, token.getLineNumber());\n            this.stream.next();\n            break;\n          case \"false\":\n          case \"FALSE\":\n            node = new LiteralBooleanExpression(false, token.getLineNumber());\n            this.stream.next();\n            break;\n          case \"none\":\n          case \"NONE\":\n          case \"null\":\n          case \"NULL\":\n            node = new LiteralNullExpression(token.getLineNumber());\n            this.stream.next();\n            break;\n\n          default:\n\n            // name of a function?\n            if (this.stream.peek().test(Token.Type.PUNCTUATION, \"(\")) {\n              node = new FunctionOrMacroNameNode(token.getValue(),\n                  this.stream.peek().getLineNumber());\n            }\n\n            // variable name\n            else {\n              node = new ContextVariableExpression(token.getValue(), token.getLineNumber());\n            }\n            this.stream.next();\n            break;\n        }\n        break;\n\n      case LONG:\n        final String longValue = token.getValue();\n        node = new LiteralLongExpression(Long.valueOf(longValue), token.getLineNumber());\n        this.stream.next();\n        break;\n\n      case NUMBER:\n        final String numberValue = token.getValue();\n        if (this.parserOptions.isLiteralNumbersAsBigDecimals()) {\n          node = new LiteralBigDecimalExpression(new BigDecimal(numberValue), token.getLineNumber());\n        } else {\n          if (numberValue.contains(\".\")) {\n            node = new LiteralDoubleExpression(Double.valueOf(numberValue), token.getLineNumber());\n          } else {\n            if (this.parserOptions.isLiteralDecimalTreatedAsInteger()) {\n              node = new LiteralIntegerExpression(Integer.valueOf(numberValue),\n                      token.getLineNumber());\n            } else {\n              node = new LiteralLongExpression(Long.valueOf(numberValue), token.getLineNumber());\n            }\n          }\n        }\n        this.stream.next();\n        break;\n\n      case STRING:\n      case STRING_INTERPOLATION_START:\n        node = this.parseStringExpression();\n        break;\n\n      // not found, syntax error\n      default:\n        throw new ParserException(null,\n            String.format(\"Unexpected token \\\"%s\\\" of value \\\"%s\\\"\", token.getType()\n                .toString(), token.getValue()), token.getLineNumber(), this.stream.getFilename());\n    }\n\n    // there may or may not be more to this expression - let's keep looking\n    return this.parsePostfixExpression(node);\n  }\n\n  private Expression<?> parseStringExpression() throws ParserException {\n    List<Expression<?>> nodes = new ArrayList<>();\n    Token.Type previousNodeType = null;\n\n    // Sequential strings are not OK, but strings can follow interpolation\n    while (true) {\n      if (this.stream.current().test(Token.Type.STRING) && Token.Type.STRING != previousNodeType) {\n        Token token = this.stream.expect(Token.Type.STRING);\n        nodes.add(new LiteralStringExpression(token.getValue(), token.getLineNumber()));\n        previousNodeType = Token.Type.STRING;\n      } else if (this.stream.current().test(Token.Type.STRING_INTERPOLATION_START)) {\n        this.stream.expect(Token.Type.STRING_INTERPOLATION_START);\n        nodes.add(this.parseExpression());\n        this.stream.expect(Token.Type.STRING_INTERPOLATION_END);\n        previousNodeType = Token.Type.STRING_INTERPOLATION_END;\n      } else {\n        break;\n      }\n    }\n\n    Expression<?> first = nodes.remove(0);\n    if (nodes.isEmpty()) {\n      return first;\n    }\n\n    ConcatenateExpression expr, firstExpr;\n    expr = firstExpr = new ConcatenateExpression(first, null);\n\n    for (int i = 0; i < nodes.size(); i++) {\n      Expression<?> node = nodes.get(i);\n      if (i == nodes.size() - 1) {\n        expr.setRight(node);\n      } else {\n        ConcatenateExpression newExpr = new ConcatenateExpression(node, null);\n        expr.setRight(newExpr);\n        expr = newExpr;\n      }\n    }\n\n    return firstExpr;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private Expression<?> parseTernaryExpression(Expression<?> expression) {\n    // if the next token isn't a ?, we're not dealing with a ternary\n    // expression\n    if (!this.stream.current().test(Token.Type.PUNCTUATION, \"?\")) {\n      return expression;\n    }\n\n    this.stream.next();\n    Expression<?> expression2 = this.parseExpression();\n    this.stream.expect(Token.Type.PUNCTUATION, \":\");\n    Expression<?> expression3 = this.parseExpression();\n\n    expression = new TernaryExpression((Expression<Boolean>) expression, expression2, expression3,\n        this.stream\n            .current().getLineNumber(), this.stream.getFilename());\n    return expression;\n  }\n\n  /**\n   * Determines if there is more to the provided expression than we originally thought. We will look\n   * for the filter operator or perhaps we are getting an attribute from a variable (ex.\n   * var.attribute or var['attribute'] or var.attribute(bar)).\n   *\n   * @param node The expression that we have already discovered\n   * @return Either the original expression that was passed in or a slightly modified version of it,\n   * depending on what was discovered.\n   */\n  private Expression<?> parsePostfixExpression(Expression<?> node) {\n    Token current;\n    while (true) {\n      current = this.stream.current();\n\n      if (current.test(Token.Type.PUNCTUATION, \".\") || current.test(Token.Type.PUNCTUATION, \"[\")) {\n\n        // a period represents getting an attribute from a variable or\n        // calling a method\n        node = this.parseBeanAttributeExpression(node);\n\n      } else if (current.test(Token.Type.PUNCTUATION, \"(\")) {\n\n        // function call\n        node = this.parseFunctionOrMacroInvocation(node);\n\n      } else {\n        break;\n      }\n    }\n    return node;\n  }\n\n  private Expression<?> parseFunctionOrMacroInvocation(Expression<?> node) {\n    String functionName = ((FunctionOrMacroNameNode) node).getName();\n    ArgumentsNode args = this.parseArguments();\n\n    /*\n     * The following core functions have their own Nodes and are rendered in\n     * unique ways for the sake of performance.\n     */\n    switch (functionName) {\n      case \"parent\":\n        return new ParentFunctionExpression(this.parser.peekBlockStack(),\n            this.stream.current().getLineNumber());\n      case \"block\":\n        return new BlockFunctionExpression(args, node.getLineNumber());\n    }\n\n    return new FunctionOrMacroInvocationExpression(functionName, args, node.getLineNumber());\n  }\n\n  public FilterInvocationExpression parseFilterInvocationExpression() {\n    TokenStream stream = this.parser.getStream();\n    Token filterToken = stream.expect(Token.Type.NAME);\n\n    ArgumentsNode args;\n    if (stream.current().test(Token.Type.PUNCTUATION, \"(\")) {\n      args = this.parseArguments();\n    } else {\n      args = new ArgumentsNode(null, null, filterToken.getLineNumber());\n    }\n\n    return new FilterInvocationExpression(filterToken.getValue(), args,\n        filterToken.getLineNumber());\n  }\n\n  private Expression<?> parseTestInvocationExpression() {\n    TokenStream stream = this.parser.getStream();\n    int lineNumber = stream.current().getLineNumber();\n\n    Token testToken = stream.expect(Token.Type.NAME);\n\n    ArgumentsNode args;\n    if (stream.current().test(Token.Type.PUNCTUATION, \"(\")) {\n      args = this.parseArguments();\n    } else {\n      args = new ArgumentsNode(null, null, testToken.getLineNumber());\n    }\n\n    return new TestInvocationExpression(lineNumber, testToken.getValue(), args);\n  }\n\n  /**\n   * A bean attribute expression can either be an expression getting an attribute from a variable in\n   * the context, or calling a method from a variable.\n   *\n   * Ex. foo.bar or foo['bar'] or foo.bar('baz')\n   *\n   * @param node The expression parsed so far\n   * @return NodeExpression The parsed subscript expression\n   */\n  private Expression<?> parseBeanAttributeExpression(Expression<?> node) {\n    TokenStream stream = this.parser.getStream();\n\n    if (stream.current().test(Token.Type.PUNCTUATION, \".\")) {\n\n      // skip over the '.' token\n      stream.next();\n\n      Token token = stream.expect(Token.Type.NAME);\n\n      ArgumentsNode args = null;\n      if (stream.current().test(Token.Type.PUNCTUATION, \"(\")) {\n        args = this.parseArguments();\n        if (!args.getNamedArgs().isEmpty()) {\n          throw new ParserException(null, \"Can not use named arguments when calling a bean method\",\n              stream\n                  .current().getLineNumber(), stream.getFilename());\n        }\n      }\n\n      node = new GetAttributeExpression(node,\n          new LiteralStringExpression(token.getValue(), token.getLineNumber()), args,\n          stream.getFilename(), token.getLineNumber());\n\n    } else if (stream.current().test(Token.Type.PUNCTUATION, \"[\")) {\n      // skip over opening '[' bracket\n      stream.next();\n\n      node = new GetAttributeExpression(node, this.parseExpression(), stream.getFilename(),\n          stream.current()\n              .getLineNumber());\n\n      // move past the closing ']' bracket\n      stream.expect(Token.Type.PUNCTUATION, \"]\");\n    }\n\n    return node;\n  }\n\n  private ArgumentsNode parseArguments() {\n    return this.parseArguments(false);\n  }\n\n  public ArgumentsNode parseArguments(boolean isMacroDefinition) {\n\n    List<PositionalArgumentNode> positionalArgs = new ArrayList<>();\n    List<NamedArgumentNode> namedArgs = new ArrayList<>();\n    this.stream = this.parser.getStream();\n\n    this.stream.expect(Token.Type.PUNCTUATION, \"(\");\n\n    while (!this.stream.current().test(Token.Type.PUNCTUATION, \")\")) {\n\n      String argumentName = null;\n      Expression<?> argumentValue = null;\n\n      if (!namedArgs.isEmpty() || !positionalArgs.isEmpty()) {\n        this.stream.expect(Token.Type.PUNCTUATION, \",\");\n      }\n\n      /*\n       * Most arguments consist of VALUES with optional NAMES but in the\n       * case of a macro definition the user is specifying NAMES with\n       * optional VALUES. Therefore the logic changes slightly.\n       */\n      if (isMacroDefinition) {\n        argumentName = this.parseNewVariableName();\n        if (this.stream.current().test(Token.Type.PUNCTUATION, \"=\")) {\n          this.stream.expect(Token.Type.PUNCTUATION, \"=\");\n          argumentValue = this.parseExpression();\n        }\n      } else {\n        if (this.stream.peek().test(Token.Type.PUNCTUATION, \"=\")) {\n          argumentName = this.parseNewVariableName();\n          this.stream.expect(Token.Type.PUNCTUATION, \"=\");\n        }\n        argumentValue = this.parseExpression();\n      }\n\n      if (argumentName == null) {\n        if (!namedArgs.isEmpty()) {\n          throw new ParserException(null,\n              \"Positional arguments must be declared before any named arguments.\",\n              this.stream.current()\n                  .getLineNumber(),\n              this.stream.getFilename());\n        }\n        positionalArgs.add(new PositionalArgumentNode(argumentValue));\n      } else {\n        namedArgs.add(new NamedArgumentNode(argumentName, argumentValue));\n      }\n\n    }\n\n    this.stream.expect(Token.Type.PUNCTUATION, \")\");\n\n    return new ArgumentsNode(positionalArgs, namedArgs, this.stream.current().getLineNumber());\n  }\n\n  /**\n   * Parses a new variable that will need to be initialized in the Java code.\n   *\n   * This is used for the set tag, the for loop, and in named arguments.\n   *\n   * @return A variable name\n   */\n  public String parseNewVariableName() {\n\n    // set the stream because this function may be called externally (for\n    // and set token parsers)\n    this.stream = this.parser.getStream();\n    Token token = stream.expect(Token.Type.NAME);\n\n    if (RESERVED_KEYWORDS.contains(token.getValue())) {\n      throw new ParserException(null,\n          String.format(\"Can not assign a value to %s\", token.getValue()),\n          token.getLineNumber(), this.stream.getFilename());\n    }\n\n    return token.getValue();\n  }\n\n  private Expression<?> parseArrayDefinitionExpression() {\n    TokenStream stream = this.parser.getStream();\n\n    // expect the opening bracket and check for an empty array\n    stream.expect(Token.Type.PUNCTUATION, \"[\");\n    if (stream.current().test(Token.Type.PUNCTUATION, \"]\")) {\n      stream.next();\n      return new ArrayExpression(stream.current().getLineNumber());\n    }\n\n    // there's at least one expression in the array\n    List<Expression<?>> elements = new ArrayList<>();\n    while (true) {\n      Expression<?> expr = this.parseExpression();\n      elements.add(expr);\n      if (stream.current().test(Token.Type.PUNCTUATION, \"]\")) {\n        // this seems to be the end of the array\n        break;\n      }\n      // expect the comma separator, until we either find a closing\n      // bracket or fail the expect\n      stream.expect(Token.Type.PUNCTUATION, \",\");\n    }\n\n    // expect the closing bracket\n    stream.expect(Token.Type.PUNCTUATION, \"]\");\n\n    return new ArrayExpression(elements, stream.current().getLineNumber());\n  }\n\n  private Expression<?> parseMapDefinitionExpression() {\n    TokenStream stream = this.parser.getStream();\n\n    // expect the opening brace and check for an empty map\n    stream.expect(Token.Type.PUNCTUATION, \"{\");\n    if (stream.current().test(Token.Type.PUNCTUATION, \"}\")) {\n      stream.next();\n      return new MapExpression(stream.current().getLineNumber());\n    }\n\n    // there's at least one expression in the map\n    Map<Expression<?>, Expression<?>> elements = new HashMap<>();\n    while (true) {\n      // key : value\n      Expression<?> keyExpr = this.parseExpression();\n      stream.expect(Token.Type.PUNCTUATION, \":\");\n      Expression<?> valueExpr = this.parseExpression();\n      elements.put(keyExpr, valueExpr);\n      if (stream.current().test(Token.Type.PUNCTUATION, \"}\")) {\n        // this seems to be the end of the map\n        break;\n      }\n      // expect the comma separator, until we either find a closing brace\n      // or fail the expect\n      stream.expect(Token.Type.PUNCTUATION, \",\");\n    }\n\n    // expect the closing brace\n    stream.expect(Token.Type.PUNCTUATION, \"}\");\n\n    return new MapExpression(elements, stream.current().getLineNumber());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/parser/Parser.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.parser;\n\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.RootNode;\n\npublic interface Parser {\n\n  RootNode parse(TokenStream stream);\n\n  BodyNode subparse();\n\n  /**\n   * Provides the stream of tokens which ultimately need to be \"parsed\" into Nodes.\n   *\n   * @return TokenStream\n   */\n  TokenStream getStream();\n\n  /**\n   * Parses the existing TokenStream, starting at the current Token, and ending when the\n   * stopCondition is fullfilled.\n   *\n   * @param stopCondition The condition to stop parsing a segment of the template.\n   * @return A node representing the parsed section\n   */\n  BodyNode subparse(StoppingCondition stopCondition);\n\n  ExpressionParser getExpressionParser();\n\n  String peekBlockStack();\n\n  String popBlockStack();\n\n  void pushBlockStack(String blockName);\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/parser/ParserImpl.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.parser;\n\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.PrintNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.RootNode;\nimport io.pebbletemplates.pebble.node.TextNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\n\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\npublic class ParserImpl implements Parser {\n\n  /**\n   * Binary operators\n   */\n  private final Map<String, BinaryOperator> binaryOperators;\n\n  /**\n   * Unary operators\n   */\n  private final Map<String, UnaryOperator> unaryOperators;\n\n  /**\n   * Token parsers\n   */\n  private final Map<String, TokenParser> tokenParsers;\n\n  /**\n   * An expression parser.\n   */\n  private ExpressionParser expressionParser;\n\n  /**\n   * The TokenStream that we are converting into an Abstract Syntax Tree.\n   */\n  private TokenStream stream;\n\n  /**\n   * TokenParser objects provided by the extensions.\n   */\n\n  /**\n   * used to keep track of the name of the block that we are currently inside of. This is purely\n   * just for the parent() function.\n   */\n  private LinkedList<String> blockStack;\n\n  /**\n   * parser options\n   */\n  private ParserOptions parserOptions;\n\n  /**\n   * Constructor\n   *\n   * @param binaryOperators A map of binary operators\n   * @param unaryOperators A map of unary operators\n   * @param tokenParsers A map of token parsers\n   */\n  public ParserImpl(Map<String, UnaryOperator> unaryOperators,\n      Map<String, BinaryOperator> binaryOperators,\n      Map<String, TokenParser> tokenParsers, ParserOptions parserOptions) {\n    this.binaryOperators = binaryOperators;\n    this.unaryOperators = unaryOperators;\n    this.tokenParsers = tokenParsers;\n    this.parserOptions = parserOptions;\n  }\n\n  @Override\n  public RootNode parse(TokenStream stream) {\n\n    // expression parser\n    this.expressionParser = new ExpressionParser(this, this.binaryOperators, this.unaryOperators,\n        this.parserOptions);\n\n    this.stream = stream;\n\n    this.blockStack = new LinkedList<>();\n\n    BodyNode body = this.subparse();\n\n    return new RootNode(body);\n  }\n\n  @Override\n  public BodyNode subparse() {\n    return this.subparse(null);\n  }\n\n  @Override\n  /**\n   * The main method for the parser. This method does the work of converting\n   * a TokenStream into a Node\n   *\n   * @param stopCondition    A stopping condition provided by a token parser\n   * @return Node        The root node of the generated Abstract Syntax Tree\n   */ public BodyNode subparse(StoppingCondition stopCondition) {\n\n    // these nodes will be the children of the root node\n    List<RenderableNode> nodes = new ArrayList<>();\n\n    Token token;\n    while (!this.stream.isEOF()) {\n\n      switch (this.stream.current().getType()) {\n        case TEXT:\n\n          /*\n           * The current token is a text token. Not much to do here other\n           * than convert it to a text Node.\n           */\n          token = this.stream.current();\n          nodes.add(new TextNode(token.getValue(), token.getLineNumber()));\n          this.stream.next();\n          break;\n\n        case PRINT_START:\n\n          /*\n           * We are entering a print delimited region at this point. These\n           * regions will contain some sort of expression so let's pass\n           * control to our expression parser.\n           */\n\n          // go to the next token because the current one is just the\n          // opening delimiter\n          token = this.stream.next();\n\n          Expression<?> expression = this.expressionParser.parseExpression();\n          nodes.add(new PrintNode(expression, token.getLineNumber()));\n\n          // we expect to see a print closing delimiter\n          this.stream.expect(Token.Type.PRINT_END);\n\n          break;\n\n        case EXECUTE_START:\n\n          // go to the next token because the current one is just the\n          // opening delimiter\n          this.stream.next();\n\n          token = this.stream.current();\n\n          /*\n           * We expect a name token at the beginning of every block.\n           *\n           * We do not use stream.expect() because it consumes the current\n           * token. The current token may be needed by a token parser\n           * which has provided a stopping condition. Ex. the 'if' token\n           * parser may need to check if the current token is either\n           * 'endif' or 'else' and act accordingly, thus we should not\n           * consume it.\n           */\n          if (!Token.Type.NAME.equals(token.getType())) {\n            throw new ParserException(null, \"A block must start with a tag name.\",\n                token.getLineNumber(),\n                this.stream.getFilename());\n          }\n\n          // If this method was executed using a TokenParser and\n          // that parser provided a stopping condition (ex. checking\n          // for the 'endif' token) let's check for that condition\n          // now.\n          if (stopCondition != null && stopCondition.evaluate(token)) {\n            return new BodyNode(token.getLineNumber(), nodes);\n          }\n\n          // find an appropriate parser for this name\n          TokenParser tokenParser = this.tokenParsers.get(token.getValue());\n\n          if (tokenParser == null) {\n            throw new ParserException(null,\n                String.format(\"Unexpected tag name \\\"%s\\\"\", token.getValue()),\n                token.getLineNumber(), this.stream.getFilename());\n          }\n\n          RenderableNode node = tokenParser.parse(token, this);\n\n          // node might be null (ex. \"extend\" token parser)\n          if (node != null) {\n            nodes.add(node);\n          }\n\n          break;\n\n        default:\n          throw new ParserException(null, \"Parser ended in undefined state.\",\n              this.stream.current().getLineNumber(),\n              this.stream.getFilename());\n      }\n    }\n\n    // create the root node with the children that we have found\n    return new BodyNode(this.stream.current().getLineNumber(), nodes);\n  }\n\n  @Override\n  public TokenStream getStream() {\n    return this.stream;\n  }\n\n  public void setStream(TokenStream stream) {\n    this.stream = stream;\n  }\n\n  @Override\n  public ExpressionParser getExpressionParser() {\n    return this.expressionParser;\n  }\n\n  @Override\n  public String peekBlockStack() {\n    return this.blockStack.peek();\n  }\n\n  @Override\n  public String popBlockStack() {\n    return this.blockStack.pop();\n  }\n\n  @Override\n  public void pushBlockStack(String blockName) {\n    this.blockStack.push(blockName);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/parser/ParserOptions.java",
    "content": "package io.pebbletemplates.pebble.parser;\n\n/**\n * Parser options.\n *\n * @author yanxiyue\n */\npublic class ParserOptions {\n\n  private boolean literalDecimalTreatedAsInteger;\n\n  private boolean literalNumbersAsBigDecimals;\n\n  public boolean isLiteralDecimalTreatedAsInteger() {\n    return literalDecimalTreatedAsInteger;\n  }\n\n  public ParserOptions setLiteralDecimalTreatedAsInteger(boolean literalDecimalTreatedAsInteger) {\n    this.literalDecimalTreatedAsInteger = literalDecimalTreatedAsInteger;\n    return this;\n  }\n\n  public boolean isLiteralNumbersAsBigDecimals() {\n    return literalNumbersAsBigDecimals;\n  }\n\n  public ParserOptions setLiteralNumbersAsBigDecimals(boolean literalNumbersAsBigDecimals) {\n    this.literalNumbersAsBigDecimals = literalNumbersAsBigDecimals;\n    return this;\n  }\n\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/parser/StoppingCondition.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.parser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\n\n/**\n * Implementations of this class are provided by the TokenParsers and handed to the main Parser. The\n * main parser will parse some of the template until the stopping condition evaluates to true; at\n * this point responsibility is transferred back to the TokenParser.\n *\n * @author Mitchell\n */\npublic interface StoppingCondition {\n\n  boolean evaluate(Token data);\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/Block.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\npublic interface Block {\n\n  String getName();\n\n  void evaluate(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n      throws IOException;\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/EvaluationContext.java",
    "content": "package io.pebbletemplates.pebble.template;\n\nimport java.util.Locale;\n\n/**\n * Created by mitchell on 2016-11-13.\n */\npublic interface EvaluationContext {\n\n  boolean isStrictVariables();\n\n  Locale getLocale();\n\n  Object getVariable(String key);\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/EvaluationContextImpl.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport io.pebbletemplates.pebble.cache.CacheKey;\nimport io.pebbletemplates.pebble.cache.PebbleCache;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.ExtensionRegistry;\nimport io.pebbletemplates.pebble.utils.Callbacks;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * An evaluation context will store all stateful data that is necessary for the evaluation of a\n * template. Passing the entire state around will assist with thread safety.\n *\n * @author Mitchell\n */\npublic class EvaluationContextImpl implements EvaluationContext, RenderedSizeContext {\n\n  private final boolean strictVariables;\n\n  /**\n   * A template will look to it's parent and children for overridden macros and other features; this\n   * inheritance chain will help the template keep track of where in the inheritance chain it\n   * currently is.\n   */\n  private final Hierarchy hierarchy;\n\n  /**\n   * A scope is a set of visible variables. A trivial template will only have one scope. New scopes\n   * are added with for loops and macros for example.\n   * <p>\n   * Most scopes will have a link to their parent scope which allow an evaluation to look up the\n   * scope chain for variables. A macro is an exception to this as it only has access to it's local\n   * variables.\n   */\n  private final ScopeChain scopeChain;\n\n  /**\n   * The locale of this template.\n   */\n  private final Locale locale;\n\n  /**\n   * The maximum size of the rendered template, in chars.\n   */\n  private final int maxRenderedSize;\n\n  /**\n   * All the available filters/tests/functions for this template.\n   */\n  private final ExtensionRegistry extensionRegistry;\n\n  /**\n   * The tag cache\n   */\n  private final PebbleCache<CacheKey, Object> tagCache;\n\n  /**\n   * The user-provided ExecutorService (can be null).\n   */\n  private final ExecutorService executorService;\n\n  /**\n   * The imported templates are used to look up macros.\n   */\n  private final List<PebbleTemplateImpl> importedTemplates;\n\n  /**\n   * The named imported templates are used to look up macros.\n   */\n  private final Map<String, PebbleTemplateImpl> namedImportedTemplates;\n\n  /**\n   * evaluation options\n   */\n  private final EvaluationOptions evaluationOptions;\n\n  /**\n   * Total number of chars written by all writers sharing this context.\n   */\n  private final AtomicInteger charsRendered = new AtomicInteger();\n\n  /**\n   * Constructor used to provide all final variables.\n   *\n   * @param self The template implementation\n   * @param strictVariables Whether strict variables is to be used\n   * @param locale The locale of the template\n   * @param extensionRegistry The extension registry\n   * @param executorService The optional executor service\n   * @param scopeChain The scope chain\n   * @param hierarchy The inheritance chain\n   * @param tagCache The cache used by the \"cache\" tag\n   */\n  public EvaluationContextImpl(PebbleTemplateImpl self, boolean strictVariables, Locale locale, int maxRenderedSize,\n      ExtensionRegistry extensionRegistry, PebbleCache<CacheKey, Object> tagCache,\n      ExecutorService executorService, List<PebbleTemplateImpl> importedTemplates,\n      Map<String, PebbleTemplateImpl> namedImportedTemplates, ScopeChain scopeChain,\n      Hierarchy hierarchy, EvaluationOptions evaluationOptions) {\n\n    if (hierarchy == null) {\n      hierarchy = new Hierarchy(self);\n    }\n\n    this.strictVariables = strictVariables;\n    this.locale = locale;\n    this.maxRenderedSize = maxRenderedSize;\n    this.extensionRegistry = extensionRegistry;\n    this.tagCache = tagCache;\n    this.executorService = executorService;\n    this.importedTemplates = importedTemplates;\n    this.namedImportedTemplates = namedImportedTemplates;\n    this.scopeChain = scopeChain;\n    this.hierarchy = hierarchy;\n    this.evaluationOptions = evaluationOptions;\n  }\n\n  /**\n   * Makes an exact copy of the evaluation context EXCEPT for the inheritance chain. This is\n   * necessary for the \"include\" tag.\n   *\n   * @param self The template implementation\n   * @return A copy of the evaluation context\n   */\n  public EvaluationContextImpl shallowCopyWithoutInheritanceChain(PebbleTemplateImpl self) {\n    EvaluationContextImpl result = new EvaluationContextImpl(self, this.strictVariables,\n        this.locale, this.maxRenderedSize, this.extensionRegistry, this.tagCache,\n        this.executorService, this.importedTemplates, this.namedImportedTemplates, this.scopeChain,\n        null, this.evaluationOptions);\n    return result;\n  }\n\n  /**\n   * Makes a \"snapshot\" of the evaluation context. The scopeChain object will be a deep copy and the\n   * imported templates will be a new list. This is used for the \"parallel\" tag.\n   *\n   * @param self The template implementation\n   * @return A copy of the evaluation context\n   */\n  public EvaluationContextImpl threadSafeCopy(PebbleTemplateImpl self) {\n    EvaluationContextImpl result = new EvaluationContextImpl(self, this.strictVariables,\n        this.locale, this.maxRenderedSize, this.extensionRegistry, this.tagCache,\n        this.executorService, new ArrayList<>(this.importedTemplates),\n        new HashMap<>(this.namedImportedTemplates), this.scopeChain.deepCopy(), this.hierarchy,\n        this.evaluationOptions);\n    return result;\n  }\n\n  /**\n   * Returns the named imported template.\n   *\n   * @return the named imported template.\n   */\n  public PebbleTemplateImpl getNamedImportedTemplate(String alias) {\n    return this.namedImportedTemplates.get(alias);\n  }\n\n  public void addNamedImportedTemplates(String alias, PebbleTemplateImpl template) {\n    if (this.namedImportedTemplates.containsKey(alias)) {\n      throw new PebbleException(null,\n          \"More than one named template can not share the same name: \" + alias);\n    }\n    this.namedImportedTemplates.put(alias, template);\n  }\n\n  /**\n   * Returns whether or not this template is being evaluated in \"strict templates\" mode\n   *\n   * @return Whether or not this template is being evaluated in \"strict templates\" mode.\n   */\n  @Override\n  public boolean isStrictVariables() {\n    return this.strictVariables;\n  }\n\n  /**\n   * Returns the locale\n   *\n   * @return The current locale\n   */\n  @Override\n  public Locale getLocale() {\n    return this.locale;\n  }\n\n  /**\n   * Returns the max rendered size.\n   * @return The max rendered size.\n   */\n  @Override\n  public int getMaxRenderedSize() {\n    return this.maxRenderedSize;\n  }\n\n  /**\n   * Returns the extension registry used to access all of the tests/filters/functions\n   *\n   * @return The extension registry\n   */\n  public ExtensionRegistry getExtensionRegistry() {\n    return this.extensionRegistry;\n  }\n\n  /**\n   * Returns the executor service if exists or null\n   *\n   * @return The executor service if exists, or null\n   */\n  public ExecutorService getExecutorService() {\n    return this.executorService;\n  }\n\n  /**\n   * Returns a list of imported templates.\n   *\n   * @return A list of imported templates.\n   */\n  public List<PebbleTemplateImpl> getImportedTemplates() {\n    return this.importedTemplates;\n  }\n\n  /**\n   * Returns the cache used for the \"cache\" tag\n   *\n   * @return The cache used for the \"cache\" tag\n   */\n  public PebbleCache<CacheKey, Object> getTagCache() {\n    return this.tagCache;\n  }\n\n  /**\n   * Returns the scope chain data structure that allows variables to be added/removed from the\n   * current scope and retrieved from the nearest visible scopes.\n   *\n   * @return The scope chain.\n   */\n  public ScopeChain getScopeChain() {\n    return this.scopeChain;\n  }\n\n  /**\n   * Returns the data structure representing the entire hierarchy of the template currently being\n   * evaluated.\n   *\n   * @return The inheritance chain\n   */\n  public Hierarchy getHierarchy() {\n    return this.hierarchy;\n  }\n\n  /**\n   * Returns the evaluation options.\n   *\n   * @return the evaluation options\n   */\n  public EvaluationOptions getEvaluationOptions() {\n    return this.evaluationOptions;\n  }\n\n  @Override\n  public Object getVariable(String key) {\n    return this.scopeChain.get(key);\n  }\n\n  private void pushScope(\n          EvaluationContextImpl newContext,\n          Map<?, ?> additionalVariables,\n          Callbacks.PebbleConsumer<EvaluationContextImpl> scopedFunction\n  ) throws IOException {\n    ScopeChain scopeChain = newContext.getScopeChain();\n\n    // push a new local scope\n    scopeChain.pushScope();\n\n    // if there are additional variables to be added to this scope, add them now\n    if(additionalVariables != null) {\n      for (Map.Entry<?, ?> entry : additionalVariables.entrySet()) {\n        scopeChain.put((String) entry.getKey(), entry.getValue());\n      }\n    }\n\n    // run the callback that needs to be scoped\n    scopedFunction.accept(newContext);\n\n    // pop the new local scope\n    scopeChain.popScope();\n  }\n\n  public void scopedShallowWithoutInheritanceChain(\n          PebbleTemplateImpl template,\n          Map<?, ?> additionalVariables,\n          Callbacks.PebbleConsumer<EvaluationContextImpl> scopedFunction) throws IOException {\n    pushScope(\n            this.shallowCopyWithoutInheritanceChain(template),\n            additionalVariables,\n            scopedFunction\n    );\n  }\n\n  @Override\n  public int addAndGet(int delta) {\n    return charsRendered.addAndGet(delta);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/EvaluationOptions.java",
    "content": "package io.pebbletemplates.pebble.template;\n\nimport io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;\n\n/**\n * Evaluation options.\n *\n * @author yanxiyue\n */\npublic class EvaluationOptions {\n\n  /**\n   * toggle to enable/disable greedy matching mode for finding java method\n   */\n  private final boolean greedyMatchMethod;\n\n  /**\n   * Validator that can be used to validate object/method access\n   */\n  private final MethodAccessValidator methodAccessValidator;\n\n  public EvaluationOptions(boolean greedyMatchMethod,\n      MethodAccessValidator methodAccessValidator) {\n    this.greedyMatchMethod = greedyMatchMethod;\n    this.methodAccessValidator = methodAccessValidator;\n  }\n\n  public boolean isGreedyMatchMethod() {\n    return this.greedyMatchMethod;\n  }\n\n  public MethodAccessValidator getMethodAccessValidator() {\n    return this.methodAccessValidator;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/GlobalContext.java",
    "content": "package io.pebbletemplates.pebble.template;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class GlobalContext implements Map<String, Object> {\n\n  private final ScopeChain scopeChain;\n\n  public GlobalContext(ScopeChain scopeChain) {\n    this.scopeChain = scopeChain;\n  }\n\n  @Override\n  public Object get(Object key) {\n    List<Scope> globalScopes = this.scopeChain.getGlobalScopes();\n    String keyAsString = String.valueOf(key);\n    for (Scope scope : globalScopes) {\n      Object result = scope.get(keyAsString);\n      if (result != null) {\n        return result;\n      } else if (scope.containsKey(keyAsString)) {\n        return null;\n      }\n    }\n\n    return null;\n  }\n\n  @Override\n  public boolean isEmpty() {\n    return false;\n  }\n\n  @Override\n  public int size() {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public boolean containsKey(Object key) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public boolean containsValue(Object value) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public Object put(String key, Object value) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public Object remove(Object key) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public void putAll(Map<? extends String, ?> m) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public void clear() {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public Set<String> keySet() {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public Collection<Object> values() {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public Set<Entry<String, Object>> entrySet() {\n    throw new UnsupportedOperationException();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/Hierarchy.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport java.util.ArrayList;\n\n/**\n * A data structure that represents the entire inheritance hierarchy of the current template and\n * tracks which level in the hierarchy we are currently evaluating.\n */\npublic class Hierarchy {\n\n  /**\n   * A list of all the templates in this hierarchy. A template at index i is the child to the\n   * template at index i+1.\n   */\n  private final ArrayList<PebbleTemplateImpl> hierarchy = new ArrayList<>(2);\n\n  /**\n   * Index of the template currently being evaluated.\n   */\n  private int current = 0;\n\n  /**\n   * Constructs an inheritance chain with one known template.\n   *\n   * @param currentTemplate The current template\n   */\n  public Hierarchy(PebbleTemplateImpl currentTemplate) {\n    this.hierarchy.add(currentTemplate);\n  }\n\n  /**\n   * Adds a known ancestor onto the inheritance chain, does not increment which template is the\n   * \"current\" template being evaluated.\n   *\n   * @param ancestor The ancestor template\n   */\n  public void pushAncestor(PebbleTemplateImpl ancestor) {\n    this.hierarchy.add(ancestor);\n  }\n\n  /**\n   * Signifies that the parent template in the hierarchy is now being evaluated so it should be\n   * considered the \"current\" template.\n   */\n  public void ascend() {\n    this.current++;\n  }\n\n  /**\n   * Signifies that the child template in the hierarchy is now being evaluated so i t should be\n   * considered the \"current\" template.\n   */\n  public void descend() {\n    this.current--;\n  }\n\n  /**\n   * Returns the child of the template currently being evaluated or null if there is no child.\n   *\n   * @return The child template if exists or null\n   */\n  public PebbleTemplateImpl getChild() {\n    if (this.current == 0) {\n      return null;\n    }\n    return this.hierarchy.get(this.current - 1);\n  }\n\n  /**\n   * Returns the parent of the template currently being evaluated or null if there is no parent.\n   *\n   * @return The parent template if exists or null\n   */\n  public PebbleTemplateImpl getParent() {\n    if (this.current == this.hierarchy.size() - 1) {\n      return null;\n    }\n    return this.hierarchy.get(this.current + 1);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/Macro.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport io.pebbletemplates.pebble.extension.NamedArguments;\nimport java.util.Map;\n\npublic interface Macro extends NamedArguments {\n\n  String getName();\n\n  String call(PebbleTemplateImpl self, EvaluationContextImpl context, Map<String, Object> args);\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/MacroAttributeProvider.java",
    "content": "package io.pebbletemplates.pebble.template;\n\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\n\n\npublic class MacroAttributeProvider {\n\n  private final PebbleTemplateImpl template;\n\n  public MacroAttributeProvider(PebbleTemplateImpl template) {\n    this.template = template;\n  }\n\n  /**\n   * Invokes a macro\n   *\n   * @param context The evaluation context\n   * @param macroName The name of the macro\n   * @param args The arguments\n   * @param ignoreOverriden Whether or not to ignore macro definitions in child template\n   * @return The results of the macro invocation\n   */\n  public Object macro(EvaluationContextImpl context, String macroName, ArgumentsNode args,\n      boolean ignoreOverriden, int lineNumber) {\n    return template.macro(context, macroName, args, ignoreOverriden, lineNumber);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/PebbleTemplate.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.node.RenderableNode;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.Locale;\nimport java.util.Map;\n\n/**\n * A template object constructed by an instance of {@link PebbleEngine}.\n * A template by itself is stateless and can therefore be re-used over and over to provide different\n * outputs depending on the variables that are provided at the time of evaluation.\n */\npublic interface PebbleTemplate {\n\n  /**\n   * Evaluate the template without any provided variables. This will use the default locale provided\n   * by the {@link PebbleEngine}.\n   *\n   * @param writer The results of the evaluation are written to this writer.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluate(Writer writer) throws IOException;\n\n  /**\n   * Evaluate the template with a particular locale but without any provided variables.\n   *\n   * @param writer The results of the evaluation are written to this writer.\n   * @param locale The locale used during the evaluation of the template.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluate(Writer writer, Locale locale) throws IOException;\n\n  /**\n   * Evaluate the template with a set of variables and the default locale provided by the {@link\n   * PebbleEngine}\n   *\n   * @param writer The results of the evaluation are written to this writer.\n   * @param context The variables used during the evaluation of the template.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluate(Writer writer, Map<String, Object> context) throws IOException;\n\n  /**\n   * Evaluate the template with a particular locale and a set of variables.\n   *\n   * @param writer The results of the evaluation are written to this writer.\n   * @param context The variables used during the evaluation of the template.\n   * @param locale The locale used during the evaluation of the template.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluate(Writer writer, Map<String, Object> context, Locale locale) throws IOException;\n\n  /**\n   * Evaluate the template but only render the contents of a specific block.\n   *\n   * @param blockName The name of the template block to return.\n   * @param writer The results of the evaluation are written to this writer.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluateBlock(String blockName, Writer writer) throws IOException;\n\n  /**\n   * Evaluate the template but only render the contents of a specific block.\n   *\n   * @param blockName The name of the template block to return.\n   * @param writer The results of the evaluation are written to this writer.\n   * @param locale The locale used during the evaluation of the template.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluateBlock(String blockName, Writer writer, Locale locale) throws IOException;\n\n  /**\n   * Evaluate the template but only render the contents of a specific block.\n   *\n   * @param blockName The name of the template block to return.\n   * @param writer The results of the evaluation are written to this writer.\n   * @param context The variables used during the evaluation of the template.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluateBlock(String blockName, Writer writer, Map<String, Object> context)\n      throws IOException;\n\n  /**\n   * Evaluate the template but only render the contents of a specific block.\n   *\n   * @param blockName The name of the template block to return.\n   * @param writer The results of the evaluation are written to this writer.\n   * @param context The variables used during the evaluation of the template.\n   * @param locale The locale used during the evaluation of the template.\n   * @throws IOException An IO exception during the evaluation\n   */\n  void evaluateBlock(String blockName, Writer writer, Map<String, Object> context, Locale locale)\n      throws IOException;\n\n  /**\n   * Returns the name of the template\n   *\n   * @return The name of the template\n   */\n  String getName();\n\n  /**\n   * Returns the root node of the template\n   * @return The name of the template\n   */\n  RenderableNode getRootNode();\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/PebbleTemplateImpl.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.escaper.SafeString;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.BlockNode;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.RootNode;\nimport io.pebbletemplates.pebble.utils.FutureWriter;\nimport io.pebbletemplates.pebble.utils.LimitedSizeWriter;\nimport io.pebbletemplates.pebble.utils.Pair;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n/**\n * The actual implementation of a PebbleTemplate\n */\npublic class PebbleTemplateImpl implements PebbleTemplate {\n\n  /**\n   * A template has to store a reference to the main engine so that it can compile other templates\n   * when using the \"import\" or \"include\" tags.\n   * <p>\n   * It will also retrieve some stateful information such as the default locale when necessary.\n   * Luckily, the engine is immutable so this should be thread safe.\n   */\n  private final PebbleEngine engine;\n\n  /**\n   * Blocks defined inside this template.\n   */\n  private final Map<String, Block> blocks = new HashMap<>();\n\n  /**\n   * Macros defined inside this template.\n   */\n  private final Map<String, Macro> macros = new HashMap<>();\n\n  /**\n   * The root node of the AST to be rendered.\n   */\n  private final RenderableNode rootNode;\n\n  /**\n   * Name of template. Used to help with debugging.\n   */\n  private final String name;\n\n  /**\n   * Constructor\n   *\n   * @param engine The pebble engine used to construct this template\n   * @param root The root not to evaluate\n   * @param name The name of the template\n   */\n  public PebbleTemplateImpl(PebbleEngine engine, RenderableNode root, String name) {\n    this.engine = engine;\n    this.rootNode = root;\n    this.name = name;\n  }\n\n  public void evaluate(Writer writer) throws IOException {\n    EvaluationContextImpl context = this.initContext(null);\n    this.evaluate(writer, context);\n  }\n\n  public void evaluate(Writer writer, Locale locale) throws IOException {\n    EvaluationContextImpl context = this.initContext(locale);\n    this.evaluate(writer, context);\n  }\n\n  public void evaluate(Writer writer, Map<String, Object> map) throws IOException {\n    EvaluationContextImpl context = this.initContext(null);\n    context.getScopeChain().pushScope(map);\n\n    // Issue #449: if the provided map is immutable, this allows us to still set variables in the template context\n    context.getScopeChain().pushScope(new HashMap<>());\n    this.evaluate(writer, context);\n  }\n\n  public void evaluate(Writer writer, Map<String, Object> map, Locale locale) throws IOException {\n    EvaluationContextImpl context = this.initContext(locale);\n    context.getScopeChain().pushScope(map);\n\n    // Issue #449: if the provided map is immutable, this allows us to still set variables in the template context\n    context.getScopeChain().pushScope(new HashMap<>());\n    this.evaluate(writer, context);\n  }\n\n  public void evaluateBlock(String blockName, Writer writer) throws IOException {\n    EvaluationContextImpl context = this.initContext(null);\n    this.evaluate(new NoopWriter(), context);\n\n    this.block(writer, context, blockName, false);\n    writer.flush();\n  }\n\n  public void evaluateBlock(String blockName, Writer writer, Locale locale) throws IOException {\n    EvaluationContextImpl context = this.initContext(locale);\n    this.evaluate(new NoopWriter(), context);\n\n    this.block(writer, context, blockName, false);\n    writer.flush();\n  }\n\n  public void evaluateBlock(String blockName, Writer writer, Map<String, Object> map)\n      throws IOException {\n    EvaluationContextImpl context = this.initContext(null);\n    context.getScopeChain().pushScope(map);\n    this.evaluate(new NoopWriter(), context);\n\n    this.block(writer, context, blockName, false);\n    writer.flush();\n  }\n\n  public void evaluateBlock(String blockName, Writer writer, Map<String, Object> map, Locale locale)\n      throws IOException {\n    EvaluationContextImpl context = this.initContext(locale);\n    context.getScopeChain().pushScope(map);\n    this.evaluate(new NoopWriter(), context);\n\n    this.block(writer, context, blockName, false);\n    writer.flush();\n  }\n\n  /**\n   * This is the authoritative evaluate method. It will evaluate the template starting at the root\n   * node.\n   *\n   * @param writer The writer used to write the final output of the template\n   * @param context The evaluation context\n   * @throws IOException Thrown from the writer object\n   */\n  private void evaluate(Writer writer, EvaluationContextImpl context) throws IOException {\n    if (context.getExecutorService() != null) {\n      writer = new FutureWriter(writer);\n    }\n    writer = LimitedSizeWriter.from(writer, context);\n    this.rootNode.render(this, writer, context);\n\n    /*\n     * If the current template has a parent then we know the current template\n     * was only used to evaluate a very small subset of tags such as \"set\" and \"import\".\n     * We now evaluate the parent template as to evaluate all of the actual content.\n     * When evaluating the parent template, it will check the child template for overridden blocks.\n     */\n    if (context.getHierarchy().getParent() != null) {\n      PebbleTemplateImpl parent = context.getHierarchy().getParent();\n      context.getHierarchy().ascend();\n      parent.evaluate(writer, context);\n    }\n    writer.flush();\n  }\n\n  /**\n   * Initializes the evaluation context with settings from the engine.\n   *\n   * @param locale The desired locale\n   * @return The evaluation context\n   */\n  private EvaluationContextImpl initContext(Locale locale) {\n    locale = locale == null ? this.engine.getDefaultLocale() : locale;\n\n    // globals\n    ScopeChain scopeChain = new ScopeChain();\n    Map<String, Object> globals = new HashMap<>();\n    globals.put(\"locale\", locale);\n    globals.put(\"template\", this);\n    globals.put(\"_context\", new GlobalContext(scopeChain));\n\n    scopeChain.pushScope(globals);\n\n    // global vars provided from extensions\n    scopeChain.pushScope(this.engine.getExtensionRegistry().getGlobalVariables());\n\n    return new EvaluationContextImpl(this, this.engine.isStrictVariables(), locale, this.engine.getMaxRenderedSize(),\n        this.engine.getExtensionRegistry(), this.engine.getTagCache(),\n        this.engine.getExecutorService(),\n        new ArrayList<>(), new HashMap<>(), scopeChain, null, this.engine.getEvaluationOptions());\n  }\n\n  /**\n   * Return a shallow copy of this template.\n   *\n   * @return A new template instance with the same data\n   */\n  private PebbleTemplateImpl shallowCopy() {\n    PebbleTemplateImpl copy = new PebbleTemplateImpl(engine, rootNode, name);\n    copy.blocks.putAll(this.blocks);\n    copy.macros.putAll(this.macros);\n\n    return copy;\n  }\n\n  /**\n   * Imports a template.\n   *\n   * @param context The evaluation context\n   * @param name The template name\n   */\n  public void importTemplate(EvaluationContextImpl context, String name) {\n    context.getImportedTemplates()\n        .add((PebbleTemplateImpl) this.engine.getTemplate(this.resolveRelativePath(name)));\n  }\n\n  /**\n   * Imports a named template.\n   *\n   * @param context The evaluation context\n   * @param name The template name\n   * @param alias The template alias\n   */\n  public void importNamedTemplate(EvaluationContextImpl context, String name, String alias) {\n    context.addNamedImportedTemplates(alias,\n        (PebbleTemplateImpl) this.engine.getTemplate(this.resolveRelativePath(name)));\n  }\n\n  /**\n   * Imports named macros from specified template.\n   *\n   * @param name The template name\n   * @param namedMacros named macros\n   */\n  public void importNamedMacrosFromTemplate(String name, List<Pair<String, String>> namedMacros) {\n    PebbleTemplateImpl templateImpl = (PebbleTemplateImpl) this.engine\n        .getTemplate(this.resolveRelativePath(name));\n    for (Pair<String, String> pair : namedMacros) {\n      Macro m = templateImpl.macros.get(pair.getRight());\n\n      if (m == null) {\n        throw new PebbleException(null, \"Function or Macro [\" + pair.getRight() + \"] referenced by alias [\"\n                + pair.getLeft() + \"] does not exist.\");\n      }\n\n      this.registerMacro(pair.getLeft(), m);\n    }\n  }\n\n  /**\n   * Returns a named template.\n   *\n   * @param context The evaluation context\n   * @param alias The template alias\n   */\n  public PebbleTemplateImpl getNamedImportedTemplate(EvaluationContextImpl context, String alias) {\n    return context.getNamedImportedTemplate(alias);\n  }\n\n  /**\n   * Includes a template with {@code name} into this template.\n   *\n   * @param writer the writer to which the output should be written to.\n   * @param context the context within which the template is rendered in.\n   * @param name the name of the template to include.\n   * @param additionalVariables the map with additional variables provided with the include tag to\n   * add within the include tag.\n   * @throws IOException Any error during the loading of the template\n   */\n  public void includeTemplate(Writer writer, EvaluationContextImpl context, String name,\n      Map<?, ?> additionalVariables) throws IOException {\n    PebbleTemplateImpl template = (PebbleTemplateImpl) this.engine\n        .getTemplate(this.resolveRelativePath(name));\n    EvaluationContextImpl newContext = context.shallowCopyWithoutInheritanceChain(template);\n    ScopeChain scopeChain = newContext.getScopeChain();\n    scopeChain.pushScope();\n    for (Entry<?, ?> entry : additionalVariables.entrySet()) {\n      scopeChain.put((String) entry.getKey(), entry.getValue());\n    }\n    template.evaluate(writer, newContext);\n    scopeChain.popScope();\n  }\n\n  /**\n   * Embed a template with {@code name} into this template and override its child blocks. This has the effect of\n   * essentially \"including\" a template (as with the `include` tag), but its blocks may be overridden in the calling\n   * template similar to extending a template.\n   *\n   * @param lineNo the line number of the node being evaluated\n   * @param writer the writer to which the output should be written to.\n   * @param context the context within which the template is rendered in.\n   * @param name the name of the template to include.\n   * @param additionalVariables the map with additional variables provided with the include tag to\n   * add within the embed tag.\n   * @param overriddenBlocks the blocks parsed out of the parent template that should override blocks in the embedded template\n   * @throws IOException Any error during the loading of the template\n   */\n  public void embedTemplate(\n          int lineNo,\n          Writer writer,\n          EvaluationContextImpl context,\n          String name,\n          Map<?, ?> additionalVariables,\n          List<BlockNode> overriddenBlocks\n  ) throws IOException {\n    // get the template to embed\n    String embeddedTemplateName = this.resolveRelativePath(name);\n\n    // make a shallow copy of the template so we can safely modify its blocks without affecting other templates in the\n    // template cache. Include and extend will use the same object from the cache, so we need to make sure embeds do not\n    // impact those other tags or change anything in the cache.\n    final PebbleTemplateImpl embeddedTemplate =\n            ((PebbleTemplateImpl) this.engine.getTemplate(embeddedTemplateName)).shallowCopy();\n\n    // push a child scope based on the current scope\n    context.scopedShallowWithoutInheritanceChain(embeddedTemplate, additionalVariables, (newContext) -> {\n\n      // create a fake root template to act as the parent of the embedded template. That root node simply renders the\n      // embedded template's own RootNode, but now we're able to isolate its template hierarchy and provide new blocks\n      // into that hierarchy\n      BodyNode embeddedTemplateBody = ((RootNode) embeddedTemplate.rootNode).getBody();\n      BodyNode bodyNode = new BodyNode(lineNo, Collections.singletonList(embeddedTemplateBody));\n      PebbleTemplateImpl fakeRootTemplate = new PebbleTemplateImpl(engine, bodyNode, embeddedTemplateName);\n\n      // push the blocks from the embedded template into the fake root, to make sure they are able to rendered if they\n      // are not overridden\n      for(Block block : embeddedTemplate.blocks.values()) {\n        fakeRootTemplate.registerBlock(block);\n      }\n\n      // push the overridden blocks into the embedded template, since they were added to the host template rather than\n      // the embdedded template during parsing. Overridden blocks must be present in the embedded template.\n      for(BlockNode blockNode : overriddenBlocks) {\n        embeddedTemplate.registerBlock(blockNode.getBlock());\n      }\n\n      // push the new fake template root into the child context so blocks are resolved properly.\n      newContext.getHierarchy().pushAncestor(fakeRootTemplate);\n\n      // evaluate the embedded template. Its blocks will now override those defined in the fake root template using the\n      // same mechanism as for overriding blocks when extending a template\n      embeddedTemplate.evaluate(writer, newContext);\n    });\n  }\n\n  /**\n   * Checks if a macro exists\n   *\n   * @param macroName The name of the macro\n   * @return Whether or not the macro exists\n   */\n  public boolean hasMacro(String macroName) {\n    return this.macros.containsKey(macroName);\n  }\n\n  /**\n   * Checks if a block exists\n   *\n   * @param blockName The name of the block\n   * @return Whether or not the block exists\n   */\n  public boolean hasBlock(String blockName) {\n    return this.blocks.containsKey(blockName);\n  }\n\n  /**\n   * This method resolves the given relative path based on this template file path.\n   *\n   * @param relativePath the path which should be resolved.\n   * @return the resolved path.\n   */\n  public String resolveRelativePath(String relativePath) {\n    String resolved = this.engine.getLoader().resolveRelativePath(relativePath, this.name);\n    if (resolved == null) {\n      return relativePath;\n    } else {\n      return resolved;\n    }\n  }\n\n  /**\n   * Registers a block.\n   *\n   * @param block The block\n   */\n  public void registerBlock(Block block) {\n    this.blocks.put(block.getName(), block);\n  }\n\n  /**\n   * Registers a macro\n   *\n   * @param macro The macro\n   */\n  public void registerMacro(Macro macro) {\n    if (this.macros.containsKey(macro.getName())) {\n      throw new PebbleException(null,\n          \"More than one macro can not share the same name: \" + macro.getName());\n    }\n    this.macros.put(macro.getName(), macro);\n  }\n\n  /**\n   * Registers a macro with alias\n   *\n   * @param macro The macro\n   * @throws PebbleException Throws exception if macro already exists with the same name\n   */\n  public void registerMacro(String alias, Macro macro) {\n    if (this.macros.containsKey(alias)) {\n      throw new PebbleException(null, \"More than one macro can not share the same name: \" + alias);\n    }\n    this.macros.put(alias, macro);\n  }\n\n  /**\n   * A typical block declaration will use this method which evaluates the block using the regular\n   * user-provided writer.\n   *\n   * @param blockName The name of the block\n   * @param context The evaluation context\n   * @param ignoreOverriden Whether or not to ignore overriden blocks\n   * @param writer The writer\n   * @throws IOException Thrown from the writer object\n   */\n  public void block(Writer writer, EvaluationContextImpl context, String blockName,\n      boolean ignoreOverriden) throws IOException {\n\n    Hierarchy hierarchy = context.getHierarchy();\n    PebbleTemplateImpl childTemplate = hierarchy.getChild();\n\n    // check child\n    if (!ignoreOverriden && childTemplate != null) {\n      hierarchy.descend();\n      childTemplate.block(writer, context, blockName, false);\n      hierarchy.ascend();\n\n      // check this template\n    } else if (this.blocks.containsKey(blockName)) {\n      Block block = this.blocks.get(blockName);\n      block.evaluate(this, writer, context);\n\n      // delegate to parent\n    } else {\n      if (hierarchy.getParent() != null) {\n        PebbleTemplateImpl parent = hierarchy.getParent();\n        hierarchy.ascend();\n        parent.block(writer, context, blockName, true);\n        hierarchy.descend();\n      }\n    }\n\n  }\n\n  /**\n   * Invokes a macro\n   *\n   * @param context The evaluation context\n   * @param macroName The name of the macro\n   * @param args The arguments\n   * @param ignoreOverriden Whether or not to ignore macro definitions in child template\n   * @return The results of the macro invocation\n   */\n  public SafeString macro(EvaluationContextImpl context, String macroName, ArgumentsNode args,\n      boolean ignoreOverriden, int lineNumber) {\n    SafeString result = null;\n    boolean found = false;\n\n    PebbleTemplateImpl childTemplate = context.getHierarchy().getChild();\n\n    // check child template first\n    if (!ignoreOverriden && childTemplate != null) {\n      found = true;\n      context.getHierarchy().descend();\n      result = childTemplate.macro(context, macroName, args, false, lineNumber);\n      context.getHierarchy().ascend();\n\n      // check current template\n    } else if (this.hasMacro(macroName)) {\n      found = true;\n      Macro macro = this.macros.get(macroName);\n\n      Map<String, Object> namedArguments = args.getArgumentMap(this, context, macro);\n      result = new SafeString(macro.call(this, context, namedArguments));\n    }\n\n    // check imported templates\n    if (!found) {\n      for (PebbleTemplateImpl template : context.getImportedTemplates()) {\n        if (template.hasMacro(macroName)) {\n          found = true;\n          result = template.macro(context, macroName, args, false, lineNumber);\n          // If a macro was found and executed, dont search for more\n          break;\n        }\n      }\n    }\n\n    // delegate to parent template\n    if (!found) {\n      if (context.getHierarchy().getParent() != null) {\n        PebbleTemplateImpl parent = context.getHierarchy().getParent();\n        context.getHierarchy().ascend();\n        result = parent.macro(context, macroName, args, true, lineNumber);\n        context.getHierarchy().descend();\n      } else {\n        throw new PebbleException(null,\n            String.format(\"Function or Macro [%s] does not exist.\", macroName), lineNumber,\n            this.name);\n      }\n    }\n\n    return result;\n  }\n\n  public void setParent(EvaluationContextImpl context, String parentName) {\n    context.getHierarchy()\n        .pushAncestor(\n            (PebbleTemplateImpl) this.engine.getTemplate(this.resolveRelativePath(parentName)));\n  }\n\n  /**\n   * Returns the template name\n   *\n   * @return The name of the template\n   */\n  public String getName() {\n    return this.name;\n  }\n\n  /**\n   * Returns the root node of the template AST\n   *\n   * @return The root node of the template AST\n   */\n  public RenderableNode getRootNode() { return this.rootNode; }\n\n  private static class NoopWriter extends Writer {\n\n    public void write(char[] cbuf, int off, int len) {\n    }\n\n    public void flush() {\n    }\n\n    public void close() {\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/RenderedSizeContext.java",
    "content": "package io.pebbletemplates.pebble.template;\n\npublic interface RenderedSizeContext {\n    int getMaxRenderedSize();\n\n    int addAndGet(int delta);\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/Scope.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport java.util.HashMap;\nimport java.util.Set;\nimport java.util.Map;\n\n/**\n * A scope is a map of variables. A \"local\" scope ensures that the search for a particular variable\n * will end at this scope whether or not it was found.\n *\n * @author Mitchell\n */\npublic class Scope {\n\n  /**\n   * A \"local\" scope ensures that the search for a particular variable will end at this scope\n   * whether or not it was found.\n   */\n  private final boolean local;\n\n  /**\n   * The map of variables known at this scope\n   */\n  private final Map<String, Object> backingMap;\n\n  /**\n   * Constructor\n   *\n   * @param backingMap The backing map of variables\n   * @param local Whether this scope is local or not\n   */\n  public Scope(Map<String, Object> backingMap, boolean local) {\n    this.backingMap = backingMap == null ? new HashMap<>() : backingMap;\n    this.local = local;\n  }\n\n  /**\n   * Creates a shallow copy of the Scope.\n   * <p>\n   * This is used for the parallel tag  because every new thread should have a \"snapshot\" of the\n   * scopes, i.e. one thread should not affect rendering output of another.\n   * <p>\n   * It will construct a new collection but it will contain references to all of the original\n   * variables therefore it is not a deep copy. This is why it is import for the user to use\n   * thread-safe variables when using the parallel tag.\n   *\n   * @return A copy of the scope\n   */\n  public Scope shallowCopy() {\n    Map<String, Object> backingMapCopy = new HashMap<>(this.backingMap);\n    return new Scope(backingMapCopy, this.local);\n  }\n\n  /**\n   * Adds a variable to this scope\n   *\n   * @param key The name of the variable\n   * @param value The value of the variable\n   */\n  public void put(String key, Object value) {\n    this.backingMap.put(key, value);\n  }\n\n  /**\n   * Retrieves the variable at this scope\n   *\n   * @param key The name of the variable\n   * @return The value of the variable\n   */\n  public Object get(String key) {\n    return this.backingMap.get(key);\n  }\n\n  /**\n   * Checks if this scope contains a variable of a certain name.\n   *\n   * @param key The name of the variable\n   * @return boolean stating whether or not the backing map of this scope contains that variable\n   */\n  public boolean containsKey(String key) {\n    return this.backingMap.containsKey(key);\n  }\n\n  /**\n   * Returns whether or not this scope is \"local\".\n   *\n   * @return boolean stating whether this scope is local or not.\n   */\n  public boolean isLocal() {\n    return this.local;\n  }\n\n  /**\n   * Returns keys of all the variables at this scope.\n   *\n   * @return A set of keys\n   */\n  public Set<String> getKeys(){\n    return backingMap.keySet();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/template/ScopeChain.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * A stack data structure used to represent the scope of variables that are currently accessible.\n * Pushing a new scope will allow the template to add variables with names of pre-existing variables\n * without overriding the originals; to access the original variables you would pop the scope\n * again.\n */\npublic class ScopeChain {\n\n  /**\n   * The stack of scopes\n   */\n  private LinkedList<Scope> stack = new LinkedList<>();\n\n  /**\n   * Constructs an empty scope chain without any known scopes.\n   */\n  public ScopeChain() {\n  }\n\n  /**\n   * Creates a deep copy of the ScopeChain. This is used for the parallel tag because every new\n   * thread should have a \"snapshot\" of the scopes, i.e. if one thread adds a new object to a scope,\n   * it should not be available to the other threads.\n   * <p>\n   * This will construct a new scope chain and new scopes but it will continue to have references to\n   * the original user-provided variables. This is why it is important for the user to only provide\n   * thread-safe variables when using the \"parallel\" tag.\n   *\n   * @return A copy of the scope chain\n   */\n  public ScopeChain deepCopy() {\n    ScopeChain copy = new ScopeChain();\n\n    for (Scope originalScope : this.stack) {\n      copy.stack.add(originalScope.shallowCopy());\n    }\n    return copy;\n  }\n\n  /**\n   * Adds an empty non-local scope to the scope chain\n   */\n  public void pushScope() {\n    this.pushScope(new HashMap<>());\n  }\n\n  /**\n   * Adds a new non-local scope to the scope chain\n   *\n   * @param map The known variables of this scope.\n   */\n  public void pushScope(Map<String, Object> map) {\n    Scope scope = new Scope(map, false);\n    this.stack.push(scope);\n  }\n\n  /**\n   * Adds a new local scope to the scope chain\n   */\n  public void pushLocalScope() {\n    Scope scope = new Scope(new HashMap<>(), true);\n    this.stack.push(scope);\n  }\n\n  /**\n   * Pops the most recent scope from the scope chain.\n   */\n  public void popScope() {\n    this.stack.pop();\n  }\n\n  /**\n   * Adds a variable to the current scope.\n   *\n   * @param key The name of the variable\n   * @param value The value of the variable\n   */\n  public void put(String key, Object value) {\n    this.stack.peek().put(key, value);\n  }\n\n  /**\n   * Retrieves a variable from the scope chain, starting at the current scope and working it's way\n   * up all visible scopes.\n   *\n   * @param key The name of the variable\n   * @return The value of the variable\n   */\n  public Object get(String key) {\n    /*\n     * The majority of time, the requested variable will be in the first\n     * scope so we do a quick lookup in that scope before attempting to\n     * create an iterator, etc. This is solely for performance.\n     * null values must not be handled as \"not present\".\n     */\n    Scope scope = this.stack.getFirst();\n    Object result = scope.get(key);\n    if (result != null) {\n      return result;\n    }\n\n    if (this.stack.size() > 1) {\n      if (scope.isLocal() || scope.containsKey(key)) {\n        // key could be defined with null and override another value below in the stack\n        return null;\n      }\n      Iterator<Scope> iterator = this.stack.iterator();\n      // account for the first lookup we did\n      iterator.next();\n\n      while (iterator.hasNext()) {\n        scope = iterator.next();\n        result = scope.get(key);\n        if (result != null) {\n          return result;\n        } else if (scope.isLocal() || scope.containsKey(key)) {\n          // null value\n          return null;\n        }\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * This method checks if the given {@code key} does exists within the scope chain.\n   *\n   * @param key the for which the the check should be executed for.\n   * @return {@code true} when the key does exists or {@code false} when the given key does not\n   * exists.\n   */\n  public boolean containsKey(String key) {\n\n    /*\n     * The majority of time, the requested variable will be in the first\n     * scope so we do a quick lookup in that scope before attempting to\n     * create an iterator, etc. This is solely for performance.\n     */\n    Scope scope = this.stack.getFirst();\n    if (scope.containsKey(key)) {\n      return true;\n    }\n    if (scope.isLocal()) {\n      return false;\n    }\n\n    Iterator<Scope> iterator = this.stack.iterator();\n\n    // account for the first lookup we did\n    iterator.next();\n\n    while (iterator.hasNext()) {\n      scope = iterator.next();\n\n      if (scope.containsKey(key)) {\n        return true;\n      }\n      if (scope.isLocal()) {\n        return false;\n      }\n    }\n\n    return false;\n  }\n\n  /**\n   * Checks if the current scope contains a variable without then looking up the scope chain.\n   *\n   * @param variableName The name of the variable\n   * @return Whether or not the variable exists in the current scope\n   */\n  public boolean currentScopeContainsVariable(String variableName) {\n    return this.stack.getFirst().containsKey(variableName);\n  }\n\n  /**\n   * Sets the value of a variable in the first scope in the chain that already contains the\n   * variable; adds a variable to the current scope if an existing variable is not found.\n   *\n   * @param key The name of the variable\n   * @param value The value of the variable\n   */\n  public void set(String key, Object value) {\n    /*\n     * The majority of time, the requested variable will be in the first\n     * scope so we do a quick lookup in that scope before attempting to\n     * create an iterator, etc. This is solely for performance.\n     */\n    Scope scope = this.stack.getFirst();\n    if (scope.isLocal() || scope.containsKey(key)) {\n      scope.put(key, value);\n      return;\n    }\n\n    Iterator<Scope> iterator = this.stack.iterator();\n\n    // account for the first lookup we did\n    iterator.next();\n\n    while (iterator.hasNext()) {\n      scope = iterator.next();\n\n      if (scope.isLocal() || scope.containsKey(key)) {\n        scope.put(key, value);\n        return;\n      }\n    }\n\n    // no existing variable, create a new one\n    this.put(key, value);\n  }\n\n  public List<Scope> getGlobalScopes() {\n    List<Scope> globalScopes = new ArrayList<>();\n    Iterator<Scope> iterator = this.stack.iterator();\n    while (iterator.hasNext()) {\n      Scope scope = iterator.next();\n      if (scope.isLocal()) {\n        globalScopes.clear();\n      } else {\n        globalScopes.add(scope);\n      }\n    }\n\n    return globalScopes;\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/AutoEscapeTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.AutoEscapeNode;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class AutoEscapeTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    String strategy = null;\n    boolean active = true;\n\n    // skip over the 'autoescape' token\n    stream.next();\n\n    // did user specify active boolean?\n    if (stream.current().test(Token.Type.NAME)) {\n      active = Boolean.parseBoolean(stream.current().getValue());\n      stream.next();\n    }\n\n    // did user specify a strategy?\n    if (stream.current().test(Token.Type.STRING)) {\n      strategy = stream.current().getValue();\n      stream.next();\n    }\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    // now we parse the block body\n    BodyNode body = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endautoescape\"));\n\n    // skip the 'endautoescape' token\n    stream.next();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new AutoEscapeNode(lineNumber, body, active, strategy);\n  }\n\n  @Override\n  public String getTag() {\n    return \"autoescape\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/BlockTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BlockNode;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class BlockTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip over the 'block' token to the name token\n    Token blockName = stream.next();\n\n    // expect a name or string for the new block\n    if (!blockName.test(Token.Type.NAME) && !blockName.test(Token.Type.STRING)) {\n\n      // we already know an error has occurred but let's just call the\n      // typical \"expect\" method so that we know a proper error\n      // message is given to user\n      stream.expect(Token.Type.NAME);\n    }\n\n    // get the name of the new block\n    String name = blockName.getValue();\n\n    // skip over name\n    stream.next();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    parser.pushBlockStack(name);\n\n    // now we parse the block body\n    BodyNode blockBody = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endblock\"));\n    parser.popBlockStack();\n\n    //check endblock us exist with block or not\n    Token endblock = stream.current();\n    if (!endblock.test(Token.Type.NAME, \"endblock\")) {\n      throw new ParserException(null,\n          \"endblock tag should be present with block tag starting line number \",\n          token.getLineNumber(), stream.getFilename());\n    }\n\n    // skip the 'endblock' token\n    stream.next();\n\n    // check if user included block name in endblock\n    Token current = stream.current();\n    if (current.test(Token.Type.NAME, name) || current.test(Token.Type.STRING, name)) {\n      stream.next();\n    }\n\n    stream.expect(Token.Type.EXECUTE_END);\n    return new BlockNode(lineNumber, name, blockBody);\n  }\n\n  @Override\n  public String getTag() {\n    return \"block\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/CacheTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.CacheNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\n/**\n * Token parser for the cache tag\n *\n * @author Eric Bussieres\n */\npublic class CacheTokenParser implements TokenParser {\n\n  public static final String TAG_NAME = \"cache\";\n\n  @Override\n  public String getTag() {\n    return TAG_NAME;\n  }\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip over the 'cache' token\n    stream.next();\n\n    Expression<?> expression = parser.getExpressionParser().parseExpression();\n\n    // Skip the expression\n    stream.next();\n\n    // now we parse the cache body\n    BodyNode cacheBody = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endcache\"));\n\n    // skip the 'endcache' token\n    stream.next();\n\n    stream.expect(Token.Type.EXECUTE_END);\n    return new CacheNode(lineNumber, expression, cacheBody);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/EmbedTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BlockNode;\nimport io.pebbletemplates.pebble.node.EmbedNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.expression.MapExpression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class EmbedTokenParser implements TokenParser {\n\n  private BlockTokenParser blockTokenParser = new BlockTokenParser();\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip over the 'embed' token\n    stream.next();\n\n    Expression<?> embedExpression = parser.getExpressionParser().parseExpression();\n\n    Token current = stream.current();\n    MapExpression mapExpression = null;\n\n    // We check if there is an optional 'with' parameter on the embed tag.\n    if (current.getType().equals(Token.Type.NAME) && current.getValue().equals(\"with\")) {\n\n      // Skip over 'with'\n      stream.next();\n\n      Expression<?> parsedExpression = parser.getExpressionParser().parseExpression();\n\n      if (parsedExpression instanceof MapExpression) {\n        mapExpression = (MapExpression) parsedExpression;\n      } else {\n        throw new ParserException(null,\n            String.format(\"Unexpected expression '%1s'.\", parsedExpression\n                .getClass().getCanonicalName()), token.getLineNumber(), stream.getFilename());\n      }\n    }\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    List<BlockNode> blocks = parseBlocks(token, parser, stream);\n\n    return new EmbedNode(lineNumber, embedExpression, mapExpression, blocks);\n  }\n\n  private List<BlockNode> parseBlocks(Token token, Parser parser, TokenStream stream) {\n    List<BlockNode> blocks = new ArrayList<>();\n\n    while(true) {\n      BlockNode node = parseBlock(token, parser, stream);\n\n      if(node == null) break;\n\n      blocks.add(node);\n    }\n\n    return blocks;\n  }\n\n  private BlockNode parseBlock(Token token, Parser parser, TokenStream stream) {\n    if(stream.current().test(Token.Type.TEXT)) {\n      Token textToken = stream.expect(Token.Type.TEXT);\n      if(textToken.getValue().trim().length() > 0) {\n        throw new ParserException(null, \"A template that extends another one cannot include content outside blocks. Did you forget to put the content inside a {% block %} tag?\", textToken.getLineNumber(), stream.getFilename());\n      }\n    }\n\n    stream.expect(Token.Type.EXECUTE_START);\n\n    // we're finished with blocks, expect {% endembed %}\n    if(stream.current().test(Token.Type.NAME, \"end\" + this.getTag())) {\n      stream.expect(Token.Type.NAME, \"end\" + this.getTag());\n      stream.expect(Token.Type.EXECUTE_END);\n      return null;\n    }\n\n    // otherwise start parsing a block tag and let the actual BlockTokenParser do the rest (to make sure it parses the\n    // same as top-level blocks)\n    return (BlockNode) blockTokenParser.parse(token, parser);\n  }\n\n  @Override\n  public String getTag() {\n    return \"embed\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/ExtendsTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.ExtendsNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class ExtendsTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip the 'extends' token\n    stream.next();\n\n    Expression<?> parentTemplateExpression = parser.getExpressionParser().parseExpression();\n\n    stream.expect(Token.Type.EXECUTE_END);\n    return new ExtendsNode(lineNumber, parentTemplateExpression);\n  }\n\n  @Override\n  public String getTag() {\n    return \"extends\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/FilterTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.PrintNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.expression.FilterExpression;\nimport io.pebbletemplates.pebble.node.expression.RenderableNodeExpression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Parses the \"filter\" tag. It has nothing to do with implementing normal filters.\n */\npublic class FilterTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip the 'filter' token\n    stream.next();\n\n    List<Expression<?>> filterInvocationExpressions = new ArrayList<>();\n\n    filterInvocationExpressions.add(parser.getExpressionParser().parseFilterInvocationExpression());\n\n    while (stream.current().test(Token.Type.OPERATOR, \"|\")) {\n      // skip the '|' token\n      stream.next();\n      filterInvocationExpressions\n          .add(parser.getExpressionParser().parseFilterInvocationExpression());\n    }\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    BodyNode body = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endfilter\"));\n\n    stream.next();\n    stream.expect(Token.Type.EXECUTE_END);\n\n    Expression<?> lastExpression = new RenderableNodeExpression(body,\n        stream.current().getLineNumber());\n\n    for (Expression<?> filterInvocationExpression : filterInvocationExpressions) {\n\n      FilterExpression filterExpression = new FilterExpression();\n      filterExpression.setRight(filterInvocationExpression);\n      filterExpression.setLeft(lastExpression);\n\n      lastExpression = filterExpression;\n    }\n\n    return new PrintNode(lastExpression, lineNumber);\n  }\n\n  @Override\n  public String getTag() {\n    return \"filter\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/FlushTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.FlushNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class FlushTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip over the 'flush' token\n    stream.next();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new FlushNode(lineNumber);\n  }\n\n  @Override\n  public String getTag() {\n    return \"flush\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/ForTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.ForNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class ForTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip the 'for' token\n    stream.next();\n\n    // get the iteration variable\n    String iterationVariable = parser.getExpressionParser().parseNewVariableName();\n\n    stream.expect(Token.Type.NAME, \"in\");\n\n    // get the iterable variable\n    Expression<?> iterable = parser.getExpressionParser().parseExpression();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    BodyNode body = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"else\", \"endfor\"));\n\n    BodyNode elseBody = null;\n\n    if (stream.current().test(Token.Type.NAME, \"else\")) {\n      // skip the 'else' token\n      stream.next();\n      stream.expect(Token.Type.EXECUTE_END);\n      elseBody = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endfor\"));\n    }\n\n    if (stream.current().getValue() == null) {\n      throw new ParserException(\n          null,\n          \"Unexpected end of template. Pebble was looking for the \\\"endfor\\\" tag\",\n          stream.current().getLineNumber(), stream.getFilename());\n    }\n    // skip the 'endfor' token\n    stream.next();\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new ForNode(lineNumber, iterationVariable, iterable, body, elseBody);\n  }\n\n  @Override\n  public String getTag() {\n    return \"for\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/FromTokenParser.java",
    "content": "package io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.FromNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.parser.Parser;\nimport io.pebbletemplates.pebble.utils.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * From Token parser for\n *\n * <p>\n * {% from \"templateName\" import macroName as alias %}\n * <p>\n *\n * @author yanxiyue\n */\npublic class FromTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip over the 'from' token\n    stream.next();\n\n    // parse the teamplateName expression\n    Expression<?> fromExpression = parser.getExpressionParser().parseExpression();\n\n    // parse the import\n    List<Pair<String, String>> namedMacros = parseNamedMacros(parser);\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new FromNode(lineNumber, fromExpression, namedMacros);\n  }\n\n  private List<Pair<String, String>> parseNamedMacros(Parser parser) {\n    List<Pair<String, String>> pairs = new ArrayList<>();\n    TokenStream stream = parser.getStream();\n\n    stream.expect(Token.Type.NAME, \"import\");\n\n    Token pre, post;\n    while (!stream.current().getType().equals(Token.Type.EXECUTE_END)) {\n      pre = stream.expect(Token.Type.NAME);\n      if (stream.current().test(Token.Type.NAME, \"as\")) {\n        // Skips over 'as'\n        stream.next();\n\n        post = stream.expect(Token.Type.NAME);\n\n        pairs.add(new Pair<>(post.getValue(), pre.getValue()));\n      } else {\n        pairs.add(new Pair<>(pre.getValue(), pre.getValue()));\n      }\n\n      Token token = stream.current();\n      if (token.test(Token.Type.PUNCTUATION, \",\")) {\n        // Skips over ','\n        stream.next();\n      } else if (token.getType().equals(Token.Type.EXECUTE_END)) {\n        break;\n\n      } else {\n        String message = String.format(\n            \"Unexpected token of value \\\"%s\\\" and type %s, expected token of type %s or ',' \",\n            token.getValue(), token.getType().toString(), Token.Type.EXECUTE_END);\n        throw new ParserException(null, message, token.getLineNumber(), stream.getFilename());\n      }\n    }\n\n    return pairs;\n  }\n\n  @Override\n  public String getTag() {\n    return \"from\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/IfTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.IfNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.parser.Parser;\nimport io.pebbletemplates.pebble.parser.StoppingCondition;\nimport io.pebbletemplates.pebble.utils.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class IfTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip the 'if' token\n    stream.next();\n\n    List<Pair<Expression<?>, BodyNode>> conditionsWithBodies = new ArrayList<>();\n\n    Expression<?> expression = parser.getExpressionParser().parseExpression();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    BodyNode body = parser.subparse(DECIDE_IF_FORK);\n\n    conditionsWithBodies.add(new Pair<>(expression, body));\n\n    BodyNode elseBody = null;\n    boolean end = false;\n    while (!end) {\n      if (stream.current().getValue() == null) {\n        throw new ParserException(\n            null,\n            \"Unexpected end of template. Pebble was looking for the \\\"endif\\\" tag\",\n            stream.current().getLineNumber(), stream.getFilename());\n      }\n\n      switch (stream.current().getValue()) {\n        case \"else\":\n          stream.next();\n          stream.expect(Token.Type.EXECUTE_END);\n          elseBody = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endif\"));\n          break;\n\n        case \"elseif\":\n          stream.next();\n          expression = parser.getExpressionParser().parseExpression();\n          stream.expect(Token.Type.EXECUTE_END);\n          body = parser.subparse(DECIDE_IF_FORK);\n          conditionsWithBodies.add(new Pair<>(expression, body));\n          break;\n\n        case \"endif\":\n          stream.next();\n          end = true;\n          break;\n        default:\n          throw new ParserException(\n              null,\n              \"Unexpected end of template. Pebble was looking for the following tags \\\"else\\\", \\\"elseif\\\", or \\\"endif\\\"\",\n              stream.current().getLineNumber(), stream.getFilename());\n      }\n    }\n\n    stream.expect(Token.Type.EXECUTE_END);\n    return new IfNode(lineNumber, conditionsWithBodies, elseBody);\n  }\n\n  private static final StoppingCondition DECIDE_IF_FORK = token -> token\n      .test(Token.Type.NAME, \"elseif\", \"else\", \"endif\");\n\n  @Override\n  public String getTag() {\n    return \"if\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/ImportTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.ImportNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class ImportTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip over the 'import' token\n    stream.next();\n\n    Expression<?> importExpression = parser.getExpressionParser().parseExpression();\n\n    Token current = stream.current();\n    String alias = null;\n\n    // We check if there is an optional 'as' keyword on the import tag.\n    if (current.getType().equals(Token.Type.NAME) && current.getValue().equals(\"as\")) {\n\n      // Skip over 'as'\n      stream.next();\n\n      current = stream.expect(Token.Type.NAME);\n      alias = current.getValue();\n    }\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new ImportNode(lineNumber, importExpression, alias);\n  }\n\n  @Override\n  public String getTag() {\n    return \"import\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/IncludeTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.IncludeNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.node.expression.MapExpression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class IncludeTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip over the 'include' token\n    stream.next();\n\n    Expression<?> includeExpression = parser.getExpressionParser().parseExpression();\n\n    Token current = stream.current();\n    MapExpression mapExpression = null;\n\n    // We check if there is an optional 'with' parameter on the include tag.\n    if (current.getType().equals(Token.Type.NAME) && current.getValue().equals(\"with\")) {\n\n      // Skip over 'with'\n      stream.next();\n\n      Expression<?> parsedExpression = parser.getExpressionParser().parseExpression();\n\n      if (parsedExpression instanceof MapExpression) {\n        mapExpression = (MapExpression) parsedExpression;\n      } else {\n        throw new ParserException(null,\n            String.format(\"Unexpected expression '%1s'.\", parsedExpression\n                .getClass().getCanonicalName()), token.getLineNumber(), stream.getFilename());\n      }\n\n    }\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new IncludeNode(lineNumber, includeExpression, mapExpression);\n  }\n\n  @Override\n  public String getTag() {\n    return \"include\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/MacroTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.MacroNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class MacroTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n\n    TokenStream stream = parser.getStream();\n\n    // skip over the 'macro' token\n    stream.next();\n\n    String macroName = stream.expect(Token.Type.NAME).getValue();\n\n    ArgumentsNode args = parser.getExpressionParser().parseArguments(true);\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    // parse the body\n    BodyNode body = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endmacro\"));\n\n    // skip the 'endmacro' token\n    stream.next();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new MacroNode(macroName, args, body);\n  }\n\n  @Override\n  public String getTag() {\n    return \"macro\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/ParallelTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.BodyNode;\nimport io.pebbletemplates.pebble.node.ParallelNode;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class ParallelTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip the 'parallel' token\n    stream.next();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    BodyNode body = parser.subparse(tkn -> tkn.test(Token.Type.NAME, \"endparallel\"));\n\n    // skip the 'endparallel' token\n    stream.next();\n\n    stream.expect(Token.Type.EXECUTE_END);\n    return new ParallelNode(lineNumber, body);\n  }\n\n  @Override\n  public String getTag() {\n    return \"parallel\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/SetTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.node.SetNode;\nimport io.pebbletemplates.pebble.node.expression.Expression;\nimport io.pebbletemplates.pebble.parser.Parser;\n\npublic class SetTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n    TokenStream stream = parser.getStream();\n    int lineNumber = token.getLineNumber();\n\n    // skip the 'set' token\n    stream.next();\n\n    String name = parser.getExpressionParser().parseNewVariableName();\n\n    stream.expect(Token.Type.PUNCTUATION, \"=\");\n\n    Expression<?> value = parser.getExpressionParser().parseExpression();\n\n    stream.expect(Token.Type.EXECUTE_END);\n\n    return new SetNode(lineNumber, name, value);\n  }\n\n  @Override\n  public String getTag() {\n    return \"set\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/TokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.parser.Parser;\n\n/**\n * A TokenParser is responsible for converting a stream of Tokens into a Node. A TokenParser often\n * has to temporarily delegate responsibility to Pebble's main Parser or Pebble's ExpressionParser.\n *\n * @author Mitchell\n */\npublic interface TokenParser {\n\n  /**\n   * The \"tag\" is used to determine when to use a particular instance of a TokenParser. For example,\n   * the TokenParser that handles the \"block\" tag would return \"block\" with this method.\n   *\n   * @return The tag used to define this TokenParser.\n   */\n  String getTag();\n\n  /**\n   * The TokenParser is responsible to convert all the necessary tokens into appropriate Nodes. It\n   * can access tokens using parser.getTokenStream().\n   *\n   * The tag may be self contained like the \"extends\" tag or it may have a start and end point with\n   * content in the middle like the \"block\" tag. If it contains content in the middle, it can use\n   * parser.subparse(stopCondition) to parse the middle content at which point responsibility comes\n   * back to the TokenParser to parse the end point.\n   *\n   * It is the responsibility of the TokenParser to ensure that when it is complete, the \"current\"\n   * token of the primary Parser's TokenStream is pointing to the NEXT token. USUALLY this means the\n   * last statement in this parse method, immediately prior to the return statement, is the\n   * following which will consume one token:\n   *\n   * stream.expect(Token.Type.EXECUTE_END);\n   *\n   * Here are two relatively simple examples of how TokenParsers are implemented:\n   *\n   * - self contained: io.pebbletemplates.tokenParser.pebble.SetTokenParser - middle content:\n   * io.pebbletemplates.tokenParser.pebble.BlockTokenParser\n   *\n   * @param token The token to parse\n   * @param parser the parser which should be used to parse the token\n   * @return A node representation of the token\n   */\n  RenderableNode parse(Token token, Parser parser);\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/tokenParser/VerbatimTokenParser.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.tokenParser;\n\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.parser.Parser;\n\n/**\n * This is just a dummy class to point developers into the right direction; the verbatim tag had to\n * be implemented directly into the lexer.\n *\n * @author mbosecke\n */\npublic class VerbatimTokenParser implements TokenParser {\n\n  @Override\n  public RenderableNode parse(Token token, Parser parser) {\n\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public String getTag() {\n    return \"verbatim\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/Callbacks.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport java.io.IOException;\n\npublic class Callbacks {\n\n    public interface PebbleConsumer<T> {\n        void accept(T t) throws IOException;\n    }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/FutureWriter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.utils;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.LinkedList;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A Writer that will wrap around the user-provided writer if the user also provided an\n * ExecutorService to the main PebbleEngine. A FutureWriter is capable of handling Futures that will\n * return a string.\n *\n * It is not thread safe but that is okay. Each thread will have its own writer, provided by the\n * \"parallel\" node; i.e. they will never share writers.\n *\n * @author Mitchell\n */\npublic class FutureWriter extends Writer {\n\n  private final LinkedList<Future<String>> orderedFutures = new LinkedList<>();\n\n  private final Writer internalWriter;\n\n  private boolean closed = false;\n\n  public FutureWriter(Writer writer) {\n    this.internalWriter = writer;\n  }\n\n  public void enqueue(Future<String> future) throws IOException {\n    if (this.closed) {\n      throw new IOException(\"Writer is closed\");\n    }\n    this.orderedFutures.add(future);\n  }\n\n  @Override\n  public void write(final char[] cbuf, final int off, final int len) throws IOException {\n\n    if (this.closed) {\n      throw new IOException(\"Writer is closed\");\n    }\n\n    final String result = new String(cbuf, off, len);\n\n    if (this.orderedFutures.isEmpty()) {\n      this.internalWriter.write(result);\n    } else {\n      Future<String> future = new Future<String>() {\n\n        @Override\n        public boolean cancel(boolean mayInterruptIfRunning) {\n          return false;\n        }\n\n        @Override\n        public boolean isCancelled() {\n          return false;\n        }\n\n        @Override\n        public boolean isDone() {\n          return true;\n        }\n\n        @Override\n        public String get() {\n          return result;\n        }\n\n        @Override\n        public String get(long timeout, TimeUnit unit) {\n          return null;\n        }\n\n      };\n\n      this.orderedFutures.add(future);\n    }\n  }\n\n  @Override\n  public void flush() throws IOException {\n    for (Future<String> future: this.orderedFutures) {\n      try {\n        String result = future.get();\n        this.internalWriter.write(result);\n        this.internalWriter.flush();\n      } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n      } catch (ExecutionException e) {\n        throw new IOException(e);\n      }\n    }\n    this.orderedFutures.clear();\n  }\n\n  @Override\n  public void close() throws IOException {\n    this.flush();\n    this.internalWriter.close();\n    this.closed = true;\n\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/LimitedSizeWriter.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.RenderedSizeContext;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\n/**\n * A Writer that will wrap around the internal writer if the user also provided a limit\n * on the size of the rendered template. The context is shared between all the writers\n * used to evaluate a template: the one supplied by the user when calling template.evaluate\n * as well as any internally created writers e.g. when evaluating a macro.\n *\n * There will be false positives. For example if a function writes something and its output\n * is passed to a filter than we count both the output of the function and the output of the\n * filter, when we should only count the output of the filter. This is fine because the user\n * can increase the maximum allowable size accordingly. The purpose here is not to be precise\n * but to protect against abuse.\n *\n * If the limit is reached a PebbleException will be thrown.\n * If the limit is negative then no checks will be performed and the original writer used as is.\n *\n * This is thread-safe if RenderedSizeContext is thread-safe.\n */\npublic class LimitedSizeWriter extends Writer {\n\n    private final Writer internalWriter;\n\n    private final RenderedSizeContext context;\n\n    public static Writer from(Writer internalWriter, RenderedSizeContext context) {\n        if (context.getMaxRenderedSize() < 0) {\n            return internalWriter;\n        }\n\n        return new LimitedSizeWriter(internalWriter, context);\n    }\n\n    private LimitedSizeWriter(Writer internalWriter, RenderedSizeContext context) {\n        if (context.getMaxRenderedSize() < 0) {\n            throw new IllegalArgumentException(\"maxRenderedSize should not be negative\");\n        }\n        this.internalWriter = internalWriter;\n        this.context = context;\n    }\n\n    @Override\n    public void write(char[] cbuf, int off, int len) throws IOException {\n        if (this.willExceedMaxChars(len)) {\n            throw new PebbleException(null, String.format(\"Tried to write more than %d chars.\", this.context.getMaxRenderedSize()));\n        }\n        this.internalWriter.write(cbuf, off, len);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        this.internalWriter.flush();\n    }\n\n    @Override\n    public void close() throws IOException {\n        this.internalWriter.close();\n    }\n\n    @Override\n    public String toString() {\n        return internalWriter.toString();\n    }\n\n    // This has the side effect of incrementing the number of chars written.\n    // This is necessary to maintain thread-safety, otherwise two threads might check\n    // the size before writing, both checks might be fine but the resulting output\n    // will be greater than the limit.\n    // If internalWriter.write throws than the content written and the count of chars\n    // written will get out of sync, but that's fine because at that point we don't\n    // care about accuracy anymore.\n    private boolean willExceedMaxChars(int charsToWrite) {\n        return this.context.addAndGet(charsToWrite) > this.context.getMaxRenderedSize();\n    }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/OperatorUtils.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.utils;\n\nimport io.pebbletemplates.pebble.extension.escaper.SafeString;\n\nimport java.math.BigDecimal;\nimport java.math.MathContext;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * This class acts as a sort of wrapper around Java's built in operators. This is necessary because\n * Pebble treats all user provided variables as Objects even if they were originally primitives.\n * <p>\n * It's important that this class mimics the natural type conversion that Java will apply when\n * performing operators. This can be found in section 5.6.2 of the Java 7 spec, under Binary Numeric\n * Promotion.\n *\n * @author Mitchell\n */\npublic class OperatorUtils {\n\n  private enum Operation {\n    ADD, SUBTRACT, MULTIPLICATION, DIVISION, MODULUS\n  }\n\n  private enum Comparison {\n    GREATER_THAN, GREATER_THAN_EQUALS, LESS_THAN, LESS_THAN_EQUALS, EQUALS\n  }\n\n  public static Object add(Object op1, Object op2) {\n    if (op1 instanceof String || op2 instanceof String) {\n      return concatenateStrings(String.valueOf(op1), String.valueOf(op2));\n    } else if (op1 instanceof SafeString || op2 instanceof SafeString) {\n      return concatenateStrings(String.valueOf(op1), String.valueOf(op2));\n    } else if (op1 instanceof List) {\n      return addToList((List<?>) op1, op2);\n    }\n    return wideningConversionBinaryOperation(op1, op2, Operation.ADD);\n  }\n\n  public static Object subtract(Object op1, Object op2) {\n    if (op1 instanceof List) {\n      return subtractFromList((List<?>) op1, op2);\n    }\n    return wideningConversionBinaryOperation(op1, op2, Operation.SUBTRACT);\n  }\n\n  public static Object multiply(Object op1, Object op2) {\n    return wideningConversionBinaryOperation(op1, op2, Operation.MULTIPLICATION);\n  }\n\n  public static Object divide(Object op1, Object op2) {\n    return wideningConversionBinaryOperation(op1, op2, Operation.DIVISION);\n  }\n\n  public static Object mod(Object op1, Object op2) {\n    return wideningConversionBinaryOperation(op1, op2, Operation.MODULUS);\n  }\n\n  public static boolean equals(Object op1, Object op2) {\n    if (op1 instanceof Number && op2 instanceof Number) {\n      return wideningConversionBinaryComparison(op1, op2, Comparison.EQUALS);\n    } else if (op1 instanceof Enum<?> && op2 instanceof String) {\n      return compareEnum((Enum<?>) op1, (String) op2);\n    } else if (op2 instanceof Enum<?> && op1 instanceof String) {\n      return compareEnum((Enum<?>) op2, (String) op1);\n    } else {\n      return ((op1 == op2) || ((op1 != null) && op1.equals(op2)));\n    }\n  }\n\n  private static <T extends Enum<T>> boolean compareEnum(Enum<T> enumVariable,\n      String compareToString) {\n    return enumVariable.name().equals(compareToString);\n  }\n\n  public static boolean gt(Object op1, Object op2) {\n    return wideningConversionBinaryComparison(op1, op2, Comparison.GREATER_THAN);\n  }\n\n  public static boolean gte(Object op1, Object op2) {\n    return wideningConversionBinaryComparison(op1, op2, Comparison.GREATER_THAN_EQUALS);\n  }\n\n  public static boolean lt(Object op1, Object op2) {\n    return wideningConversionBinaryComparison(op1, op2, Comparison.LESS_THAN);\n  }\n\n  public static boolean lte(Object op1, Object op2) {\n    return wideningConversionBinaryComparison(op1, op2, Comparison.LESS_THAN_EQUALS);\n  }\n\n  public static Object unaryPlus(Object op1) {\n    return multiply(1, op1);\n  }\n\n  public static Object unaryMinus(Object op1) {\n    return multiply(-1, op1);\n  }\n\n  private static Object concatenateStrings(String op1, String op2) {\n    return op1 + op2;\n  }\n\n  /**\n   * This is not a documented feature but we are leaving this in for now. I'm unsure if there is\n   * demand for this feature.\n   */\n  @Deprecated\n  @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n  private static Object addToList(List<?> op1, Object op2) {\n    if (op2 instanceof Collection) {\n      op1.addAll((Collection) op2);\n    } else {\n      ((List<Object>) op1).add(op2);\n    }\n    return op1;\n  }\n\n  /**\n   * This is not a documented feature but we are leaving this in for now. I'm unsure if there is\n   * demand for this feature.\n   */\n  @Deprecated\n  @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n  private static Object subtractFromList(List<?> op1, Object op2) {\n    if (op2 instanceof Collection) {\n      op1.removeAll((Collection) op2);\n    } else {\n      op1.remove(op2);\n    }\n    return op1;\n  }\n\n  private static Object wideningConversionBinaryOperation(Object op1, Object op2,\n      Operation operation) {\n\n    Number num1 = toNumber(op1);\n    Number num2 = toNumber(op2);\n\n    if (num1 instanceof BigDecimal || num2 instanceof BigDecimal) {\n      return bigDecimalOperation(BigDecimal.valueOf(num1.doubleValue()),\n          BigDecimal.valueOf(num2.doubleValue()),\n          operation);\n    }\n\n    if (num1 instanceof Double || num2 instanceof Double) {\n      return doubleOperation(num1.doubleValue(), num2.doubleValue(), operation);\n    }\n\n    if (num1 instanceof Float || num2 instanceof Float) {\n      return floatOperation(num1.floatValue(), num2.floatValue(), operation);\n    }\n\n    if (num1 instanceof Long || num2 instanceof Long) {\n      return longOperation(num1.longValue(), num2.longValue(), operation);\n    }\n\n    return integerOperation(num1.intValue(), num2.intValue(), operation);\n  }\n\n  private static boolean wideningConversionBinaryComparison(Object op1, Object op2,\n      Comparison comparison) {\n    if (op1 == null || op2 == null) {\n      return false;\n    }\n\n    Number num1;\n    Number num2;\n    try {\n      num1 = (Number) op1;\n      num2 = (Number) op2;\n    } catch (ClassCastException ex) {\n      throw new RuntimeException(\n          String\n              .format(\"invalid operands for mathematical comparison [%s]\", comparison.toString()));\n    }\n\n    return doubleComparison(num1.doubleValue(), num2.doubleValue(), comparison);\n  }\n\n  private static double doubleOperation(double op1, double op2, Operation operation) {\n    switch (operation) {\n      case ADD:\n        return op1 + op2;\n      case SUBTRACT:\n        return op1 - op2;\n      case MULTIPLICATION:\n        return op1 * op2;\n      case DIVISION:\n        return op1 / op2;\n      case MODULUS:\n        return op1 % op2;\n      default:\n        throw new RuntimeException(\"Bug in OperatorUtils in pebble library\");\n    }\n  }\n\n  private static boolean doubleComparison(double op1, double op2, Comparison comparison) {\n    switch (comparison) {\n      case GREATER_THAN:\n        return op1 > op2;\n      case GREATER_THAN_EQUALS:\n        return op1 >= op2;\n      case LESS_THAN:\n        return op1 < op2;\n      case LESS_THAN_EQUALS:\n        return op1 <= op2;\n      case EQUALS:\n        return op1 == op2;\n      default:\n        throw new RuntimeException(\"Bug in OperatorUtils in pebble library\");\n    }\n  }\n\n  private static BigDecimal bigDecimalOperation(BigDecimal op1, BigDecimal op2,\n      Operation operation) {\n    switch (operation) {\n      case ADD:\n        return op1.add(op2);\n      case SUBTRACT:\n        return op1.subtract(op2);\n      case MULTIPLICATION:\n        return op1.multiply(op2, MathContext.DECIMAL128);\n      case DIVISION:\n        return op1.divide(op2, MathContext.DECIMAL128);\n      case MODULUS:\n        return op1.remainder(op2, MathContext.DECIMAL128);\n      default:\n        throw new RuntimeException(\"Bug in OperatorUtils in pebble library\");\n    }\n  }\n\n  private static Float floatOperation(Float op1, Float op2, Operation operation) {\n    switch (operation) {\n      case ADD:\n        return op1 + op2;\n      case SUBTRACT:\n        return op1 - op2;\n      case MULTIPLICATION:\n        return op1 * op2;\n      case DIVISION:\n        return op1 / op2;\n      case MODULUS:\n        return op1 % op2;\n      default:\n        throw new RuntimeException(\"Bug in OperatorUtils in pebble library\");\n    }\n  }\n\n  private static long longOperation(long op1, long op2, Operation operation) {\n    switch (operation) {\n      case ADD:\n        return op1 + op2;\n      case SUBTRACT:\n        return op1 - op2;\n      case MULTIPLICATION:\n        return op1 * op2;\n      case DIVISION:\n        return op1 / op2;\n      case MODULUS:\n        return op1 % op2;\n      default:\n        throw new RuntimeException(\"Bug in OperatorUtils in pebble library\");\n    }\n  }\n\n  private static long integerOperation(int op1, int op2, Operation operation) {\n    switch (operation) {\n      case ADD:\n        return op1 + op2;\n      case SUBTRACT:\n        return op1 - op2;\n      case MULTIPLICATION:\n        return op1 * op2;\n      case DIVISION:\n        return op1 / op2;\n      case MODULUS:\n        return op1 % op2;\n      default:\n        throw new RuntimeException(\"Bug in OperatorUtils in pebble library\");\n    }\n  }\n\n  static Number toNumber(Object obj) {\n    if (obj instanceof Number) {\n      return (Number) obj;\n    }\n\n    if (obj instanceof String) {\n      String str = (String) obj;\n\n      // If it looks like a decimal, parse as double\n      if (str.contains(\".\") || str.contains(\"e\") || str.contains(\"E\")) {\n        return Double.parseDouble(str);\n      }\n\n      // Otherwise parse as long (can hold larger values than int)\n      return Long.parseLong(str);\n    }\n\n    throw new IllegalArgumentException(\"Cannot convert to Number: \" + obj);\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/Pair.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.utils;\n\n/**\n * A small utility class used to pair relevant objects together.\n *\n * @author Mitchell\n */\npublic class Pair<L, R> {\n\n  private final L left;\n\n  private final R right;\n\n  public Pair(L left, R right) {\n    this.left = left;\n    this.right = right;\n  }\n\n  public L getLeft() {\n    return this.left;\n  }\n\n  public R getRight() {\n    return this.right;\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\"(%s, %s)\", this.left, this.right);\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/PathUtils.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Utility class to handle relative paths.\n *\n * @author Thomas Hunziker\n */\npublic final class PathUtils {\n\n  public static final Pattern PATH_SEPARATOR_REGEX = Pattern.compile(\"[\\\\\\\\/]\");\n\n  /**\n   * Resolves the given {@code relativePath} based on the given {@code anchorPath}.\n   *\n   * @param relativePath the relative path which should be resolved.\n   * @param anchorPath the anchor path based on which the relative path should be resolved on.\n   * @param expectedSeparator The character expected to be used as a separator; dictated by the\n   * Loader.\n   * @return the resolved path or {@code null} when the path could not be resolved.\n   */\n  public static String resolveRelativePath(String relativePath, String anchorPath,\n      char expectedSeparator) {\n    if (relativePath == null || relativePath.isEmpty()) {\n      return null;\n    }\n\n    // ensure both paths use the same separator character\n    relativePath = sanitize(relativePath, expectedSeparator);\n    anchorPath = sanitize(anchorPath, expectedSeparator);\n\n    if (relativePath.startsWith(\"..\" + expectedSeparator) || relativePath\n        .startsWith(\".\" + expectedSeparator)) {\n      return resolvePathInner(relativePath, anchorPath, expectedSeparator);\n    }\n\n    return null;\n  }\n\n  public static String sanitize(String path, char expectedSeparator) {\n    return PATH_SEPARATOR_REGEX.matcher(path)\n        .replaceAll(Matcher.quoteReplacement(String.valueOf(expectedSeparator)));\n  }\n\n  private static String resolvePathInner(String relativePath, String anchorPath, char separator) {\n    StringBuilder resultingPath = new StringBuilder();\n\n    for (String segment : resolvePathSegments(determineAnchorPathSegments(anchorPath, separator),\n        splitBySeparator(relativePath, separator))) {\n      resultingPath.append(segment).append(separator);\n    }\n\n    // remove the erroneous separator added at the end\n    return resultingPath.substring(0, resultingPath.length() - 1);\n  }\n\n  private static Collection<String> determineAnchorPathSegments(String anchorPath, char separator) {\n    if (anchorPath == null || anchorPath.isEmpty()) {\n      return new ArrayList<>();\n    }\n    ArrayDeque<String> anchorPathSegments = new ArrayDeque<>(\n        splitBySeparator(anchorPath, separator));\n    if (anchorPath.charAt(anchorPath.length() - 1) != separator) {\n      anchorPathSegments.pollLast();\n    }\n    return anchorPathSegments;\n  }\n\n  private static Collection<String> resolvePathSegments(Collection<String> anchorSegments,\n      Collection<String> relativeSegments) {\n    ArrayDeque<String> result = new ArrayDeque<>(anchorSegments);\n    for (String segment : relativeSegments) {\n      if (segment.equals(\".\")) {\n        // do nothing\n      } else if (segment.equals(\"..\")) {\n        result.pollLast();\n      } else {\n        result.add(segment);\n      }\n    }\n\n    return result;\n  }\n\n  private static List<String> splitBySeparator(String path, char separator) {\n    return Arrays.asList(path.split(Pattern.quote(String.valueOf(separator))));\n  }\n\n  private PathUtils() {\n    throw new IllegalAccessError();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/StringLengthComparator.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.utils;\n\npublic class StringLengthComparator implements java.util.Comparator<String> {\n\n  public static StringLengthComparator INSTANCE = new StringLengthComparator();\n\n  private StringLengthComparator() {\n  }\n\n  public int compare(String s1, String s2) {\n    return s2.length() - s1.length();\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/StringUtils.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport java.math.BigDecimal;\n\npublic class StringUtils {\n\n  public static String ltrim(String input) {\n    int i = 0;\n    while (i < input.length() && Character.isWhitespace(input.charAt(i))) {\n      i++;\n    }\n    return input.substring(i);\n  }\n\n  public static String rtrim(String input) {\n    int i = input.length() - 1;\n    while (i >= 0 && Character.isWhitespace(input.charAt(i))) {\n      i--;\n    }\n    return input.substring(0, i + 1);\n  }\n\n  /**\n   * Converts non-null objects into strings. It will use the toString() method of most objects but\n   * handles some known exceptions.\n   */\n  public static String toString(Object var) {\n    if (var == null) {\n      throw new IllegalArgumentException(\"Var can not be null\");\n    }\n    if (var instanceof BigDecimal) {\n      return ((BigDecimal) var).toPlainString();\n    } else {\n      return var.toString();\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/main/java/io/pebbletemplates/pebble/utils/TypeUtils.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.util.Date;\n\n/**\n * A small utility class to handle type operation.\n *\n * @author yanxiyue\n */\npublic class TypeUtils {\n\n  public static Object[] compatibleCast(Object[] argumentValues, Class<?>[] parameterTypes) {\n    if (argumentValues == null || parameterTypes == null) {\n      return argumentValues;\n    }\n\n    Object[] result = new Object[argumentValues.length];\n    for (int i = 0; i < result.length; i++) {\n      result[i] = compatibleCast(argumentValues[i], parameterTypes[i]);\n    }\n\n    return result;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public static <T> T compatibleCast(Object value, Class<T> type) {\n    if (value == null || type == null || type.isAssignableFrom(value.getClass())) {\n      return (T) value;\n    }\n    if (value instanceof Number) {\n      Number number = (Number) value;\n      if (type == int.class || type == Integer.class) {\n        return (T) (Integer) number.intValue();\n      }\n      if (type == long.class || type == Long.class) {\n        return (T) (Long) number.longValue();\n      }\n      if (type == double.class || type == Double.class) {\n        return (T) (Double) number.doubleValue();\n      }\n      if (type == float.class || type == Float.class) {\n        return (T) (Float) number.floatValue();\n      }\n      if (type == byte.class || type == Byte.class) {\n        return (T) (Byte) number.byteValue();\n      }\n      if (type == short.class || type == Short.class) {\n        return (T) (Short) number.shortValue();\n      }\n      if (type == BigInteger.class) {\n        return (T) BigInteger.valueOf(number.longValue());\n      }\n      if (type == BigDecimal.class) {\n        return (T) BigDecimal.valueOf(number.doubleValue());\n      }\n      if (type == Date.class) {\n        return (T) new Date(number.longValue());\n      }\n      if (type == Boolean.class) {\n        double d = number.doubleValue();\n        return (T) (Boolean) (!Double.isNaN(d) && d != 0.0);\n      }\n    }\n    if (value instanceof String) {\n      String str = (String) value;\n      if (type == Boolean.class) {\n        return (T) (Boolean) !str.isEmpty();\n      }\n    }\n    return (T) value;\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ArgumentsNodeTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.node.ArgumentsNode;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.StringWriter;\nimport java.io.Writer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n/**\n * Tests {@link ArgumentsNode}.\n */\nclass ArgumentsNodeTest {\n\n  /**\n   * Tests that the error description is clear when a invalid number of arguments are provided.\n   */\n  @Test\n  void testInvalidArgument() throws Exception {\n\n    try {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble\n          .getTemplate(\"{{ 'This is a test of the abbreviate filter' | abbreviate(16, 10) }}\");\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      fail(\"Should not be reached, because an exception is expected.\");\n    } catch (PebbleException e) {\n      assertEquals(\"{{ 'This is a test of the abbreviate filter' | abbreviate(16, 10) }}\",\n          e.getFileName());\n      assertEquals((Integer) 1, e.getLineNumber());\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ArraySyntaxTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nclass ArraySyntaxTest {\n\n  @Test\n  void testArraySyntax() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ [] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[]\", writer.toString());\n  }\n\n  @Test\n  void testSimpleArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['first-name'] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[first-name]\", writer.toString());\n  }\n\n  @Test\n  void test2ElementArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['first-name','last-name'] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[first-name, last-name]\", writer.toString());\n  }\n\n  @Test\n  void test2ElementArray2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ [ 'first-name' ,   'last-name'    ] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[first-name, last-name]\", writer.toString());\n  }\n\n  @Test\n  void testNElementArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['repeated-name','repeated-name','repeated-name','repeated-name','repeated-name','repeated-name','repeated-name'] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\n        \"[repeated-name, repeated-name, repeated-name, repeated-name, repeated-name, repeated-name, repeated-name]\",\n        writer.toString());\n  }\n\n  /**\n   * The template engine should thrown an exception when processing\n   * a template that contains incomplete array syntax.\n   */\n  @Test\n  void testIncompleteArraySyntax() throws PebbleException {\n    // given a Pebble Engine and template text that contains incomplete array syntax\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    String source = \"{{ [,] }}\";\n    // A Parser Exception should be thrown with a message indicating that there was an unexpected token\n    assertThatExceptionOfType(ParserException.class).isThrownBy(() -> {\n          pebble.getTemplate(source);\n        }\n    ).withMessageStartingWith(\"Unexpected token\");\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testArrayWithExpressions() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['one', 2, three, numbers['four']] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"three\", \"3\");\n    context.put(\"numbers\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"four\", \"4\");\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"[one, 2, 3, 4]\", writer.toString());\n  }\n\n  @SuppressWarnings({\"serial\", \"unused\"})\n  @Test\n  void testArrayWithComplexExpressions() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['one' + 'plus', 2 - 1, three.number, numbers['four'][0], numbers ['five'] .value ] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"three\", new Object() {\n\n      public final Integer number = 3;\n    });\n    context.put(\"numbers\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"four\", new String[]{\"4\"});\n        this.put(\"five\", new Object() {\n\n          private final String value = \"five\";\n\n          public String getValue() {\n            return this.value;\n          }\n        });\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"[oneplus, 1, 3, 4, five]\", writer.toString());\n  }\n\n  @Test\n  void testSetCommand() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = ['repeated-name',2*5] %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[repeated-name, 10]\", writer.toString());\n  }\n\n  @Test\n  void testSetCommand2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = ['repeated-name',2*5] %}{{ arr [1] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"10\", writer.toString());\n  }\n\n  @Test\n  void testFirstFilter() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = ['name',2*5] %}{{ arr | first }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"name\", writer.toString());\n  }\n\n  @Test\n  void testFirstFilter2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['name',2*5] | first }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"name\", writer.toString());\n  }\n\n  @Test\n  void testJoinFilter() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = ['name',2*5] %}{{ arr | join(':') }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"name:10\", writer.toString());\n  }\n\n  @Test\n  void testJoinFilter2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['name',2*5] | join(':') }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"name:10\", writer.toString());\n  }\n\n  @Test\n  void testLastFilter() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = ['name',2*5] %}{{ arr | last }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"10\", writer.toString());\n  }\n\n  @Test\n  void testLastFilter2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['name',2*5] | last }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"10\", writer.toString());\n  }\n\n  @Test\n  void testSliceFilter() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = ['name',2*5,'three',1.9] %}{{ arr | slice(1,3) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[10, three]\", writer.toString());\n  }\n\n  @Test\n  void testSliceFilter2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ['name',2*5,'three',1.9] | slice(1,3) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[10, three]\", writer.toString());\n  }\n\n  @Test\n  void testSortFilter() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [3,2,1,0] %}{{ arr | sort }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[0, 1, 2, 3]\", writer.toString());\n  }\n\n  @Test\n  void testSortFilter2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ [3,2,1,0] | sort }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[0, 1, 2, 3]\", writer.toString());\n  }\n\n  @Test\n  void testSortFilterFromArray() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ 'q,g,s,c,w' | split(',') | sort }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[c, g, q, s, w]\", writer.toString());\n  }\n\n  @Test\n  void testForTag() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set names = ['Bob','Maria','John'] %}{% for name in names %}{{ name }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"BobMariaJohn\", writer.toString());\n  }\n\n  @Test\n  void testForTag2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for name in ['Bob','Maria','John'] %}{{ name }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"BobMariaJohn\", writer.toString());\n  }\n\n  @Test\n  void testForTagInvalidIterable() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    String source = \"{% set myVar = 'somevalue' %}{% for myVal in myVar %}{{ myVal }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    try {\n      template.evaluate(writer, new HashMap<>());\n      fail(\"Expected PebbleException\");\n    } catch (PebbleException e) {\n      assertEquals(\"Not an iterable object. Value = [somevalue] (\" + source + \":1)\",\n          e.getMessage());\n    }\n  }\n\n  @Test\n  void testForElseTag() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for name in [] %}{{ name }}{% else %}{{ 'no name' }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"no name\", writer.toString());\n  }\n\n  @Test\n  void testIfTag() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set names = ['Bob','Maria','John'] %}{% if names is null %}{{ 'it is' }}{% else %}{{ 'it is not' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"it is not\", writer.toString());\n  }\n\n  @Test\n  void testIfTag2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if ['Bob','Maria','John'] is null %}{{ 'it is' }}{% else %}{{ 'it is not' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"it is not\", writer.toString());\n  }\n\n  @Test\n  void testMacroTag() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% macro firstname(names) %}{{ names | first }}{% endmacro %}{{ firstname(['Bob','Maria','John']) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @Test\n  void testMacroTagNamedArguments() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% macro firstname(names) %}{{ names | first }}{% endmacro %}{{ firstname(names=['Bob','Maria','John']) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @Test\n  void testAdditionOverloading() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [0,1] + 2 %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[0, 1, 2]\", writer.toString());\n  }\n\n  @Test\n  void testAdditionOverloading2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [0,1] + [2,3] %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[0, 1, 2, 3]\", writer.toString());\n  }\n\n  @Test\n  void testAdditionOverloading3() throws PebbleException, IOException {\n    //Arrange\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = 1 + [0,1] %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n\n    //Act + Assert\n    // A Pebble Exception should be thrown with a message indicating that the addition operation failed\n    assertThatExceptionOfType(PebbleException.class).isThrownBy(() -> { \n        template.evaluate(writer, new HashMap<>()); \n      }\n    ).withMessageStartingWith(\"Could not perform addition\");\n  }\n\n  @Test\n  void testSubtractionOverloading() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [0,1,2] - 1 %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[0, 2]\", writer.toString());\n  }\n\n  @Test\n  void testSubtractionOverloading2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [0,1,2] - [0,2,3] %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[1]\", writer.toString());\n  }\n\n  @Test\n  void testSubtractionOverloading3() throws PebbleException, IOException {\n    //Arrange\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = 1 - [0,2] %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n\n    //Act + Assert\n    // A Pebble Exception should be thrown with a message indicating that subtraction failed\n    assertThatExceptionOfType(PebbleException.class).isThrownBy(() -> {\n        template.evaluate(writer, new HashMap<>()); \n      }\n    ).withMessageStartingWith(\"Could not perform subtraction\");\n  }\n\n  @Test\n  void testEmptyTest() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [0,1,2] %}{% if arr is empty %}{{ 'true' }}{% else %}{{ 'false' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testEmptyTest2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if [0,1,2] is empty %}{{ 'true' }}{% else %}{{ 'false' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testEmptyTest3() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if [] is not empty %}{{ 'true' }}{% else %}{{ 'false' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testIterableTest() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [0,1,2] %}{% if arr is iterable %}{{ 'true' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testIterableTest2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if [0,1,2] is iterable %}{{ 'true' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [0,1,2] %}{% if arr contains 1 %}true{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if [0,1,2] contains 1 %}true{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator3() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if [0,1,2] contains [1,2] %}true{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator4() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if [0,1,2] contains 10 %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator5() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if [0,1,2] contains 1 and not ([0,1] contains 0) %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testNestedArrays() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [[]] %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[[]]\", writer.toString());\n  }\n\n  @Test\n  void testNestedArrays2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set arr = [[],['test'],[['nested'],['arrays']]] %}{{ arr }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[[], [test], [[nested], [arrays]]]\", writer.toString());\n  }\n\n  @Test\n  void testNestedArrays3() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ [[],['test'],[['nested'],['arrays']]] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[[], [test], [[nested], [arrays]]]\", writer.toString());\n  }\n\n  @Test\n  void testNestedMapInArray() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ [{1:1}] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"[{1=1}]\", writer.toString());\n  }\n\n  // subscript syntax regression tests\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testProblematicSubscriptSyntax() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ person ['first-name'] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"first-name\", \"Bob\");\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testProblematicSubscriptSyntax2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ person ['first-name'][0] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"first-name\", new String[]{\"Bob\"});\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @SuppressWarnings({\"serial\", \"unused\"})\n  @Test\n  void testProblematicSubscriptSyntax3() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ person ['first-name'] .name }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"first-name\", new Object() {\n\n          private final String name = \"Bob\";\n\n          public String getName() {\n            return this.name;\n          }\n        });\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testProblematicSubscriptSyntax4() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if person ['first-name'] == 'Bob' %}{{ person ['first-name'] }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"first-name\", \"Bob\");\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testProblematicSubscriptSyntax5() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set name = person ['first-name'] %}{{ name }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"first-name\", \"Bob\");\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/AttributeSubscriptSyntaxTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass AttributeSubscriptSyntaxTest {\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testAccessingValueWithSubscript() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ person['first-name'] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"first-name\", \"Bob\");\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testAccessingValueWithExpressionSubscript() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source1 = \"{% set var = 'apple' %}{{ colors[var] }}\";\n    PebbleTemplate template1 = pebble.getTemplate(source1);\n\n    String source2 = \"{% set var = 'pear' %}{{ colors[var] }}\";\n    PebbleTemplate template2 = pebble.getTemplate(source2);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"colors\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"apple\", \"red\");\n        this.put(\"pear\", \"green\");\n      }\n    });\n\n    Writer writer1 = new StringWriter();\n    template1.evaluate(writer1, context);\n    assertEquals(\"red\", writer1.toString());\n\n    Writer writer2 = new StringWriter();\n    template2.evaluate(writer2, context);\n    assertEquals(\"green\", writer2.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testAccessingValueWithIntegerExpressionSubscript()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source1 = \"{{ colors[one] }}\";\n    PebbleTemplate template1 = pebble.getTemplate(source1);\n\n    String source2 = \"{{ colors[two] }}\";\n    PebbleTemplate template2 = pebble.getTemplate(source2);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"colors\", new HashMap<Long, Object>() {\n\n      {\n        this.put(1L, \"red\");\n        this.put(2L, \"green\");\n      }\n    });\n    context.put(\"one\", 1L);\n    context.put(\"two\", 2L);\n\n    Writer writer1 = new StringWriter();\n    template1.evaluate(writer1, context);\n    assertEquals(\"red\", writer1.toString());\n\n    Writer writer2 = new StringWriter();\n    template2.evaluate(writer2, context);\n    assertEquals(\"green\", writer2.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testAccessingNestedValuesWithSubscript() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ person['name']['first'] }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"name\", new HashMap<String, Object>() {\n\n          {\n            this.put(\"first\", \"Bob\");\n          }\n        });\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testMixAndMatchingAttributeSyntax() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ person['name'].first }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"name\", new HashMap<String, Object>() {\n\n          {\n            this.put(\"first\", \"Bob\");\n          }\n        });\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n\n    source = \"{{ person.name['first'] }}\";\n    template = pebble.getTemplate(source);\n\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/CacheTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.security.SecureRandom;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Semaphore;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nclass CacheTest {\n\n  private static final String LINE_SEPARATOR = System.lineSeparator();\n\n  /**\n   * There was once an issue where the cache was unable to differentiate between templates of the\n   * same name but under different directories.\n   */\n  @Test\n  void templatesWithSameNameOverridingCache() throws PebbleException, IOException {\n    PebbleEngine engine = new PebbleEngine.Builder().strictVariables(false).build();\n\n    PebbleTemplate cache1 = engine.getTemplate(\"templates/cache/cache1/template.cache.peb\");\n    PebbleTemplate cache2 = engine.getTemplate(\"templates/cache/cache2/template.cache.peb\");\n\n    Writer writer1 = new StringWriter();\n    Writer writer2 = new StringWriter();\n\n    cache1.evaluate(writer1);\n    cache2.evaluate(writer2);\n\n    String cache1Output = writer1.toString();\n    String cache2Output = writer2.toString();\n\n    assertNotEquals(cache1Output, cache2Output);\n\n  }\n\n  /**\n   * There was an issue where each template was storing a reference to it's child and this was being\n   * cached. This is an issue because a template can have many different children.\n   */\n  @Test\n  void ensureChildTemplateNotCached() throws PebbleException, IOException {\n    PebbleEngine engine = new PebbleEngine.Builder().strictVariables(false).build();\n\n    PebbleTemplate cache1 = engine.getTemplate(\"templates/cache/template.cacheChild.peb\");\n    PebbleTemplate cache2 = engine.getTemplate(\"templates/cache/template.cacheParent.peb\");\n\n    Writer writer1 = new StringWriter();\n    Writer writer2 = new StringWriter();\n\n    cache1.evaluate(writer1);\n    cache2.evaluate(writer2);\n\n    String cache1Output = writer1.toString();\n    String cache2Output = writer2.toString();\n\n    assertEquals(\"child\", cache1Output);\n    assertEquals(\"parent\", cache2Output);\n\n  }\n\n  /**\n   * An issue occurred where the engine would mistake the existence of the template in it's cache\n   * with the existence of the templates bytecode in the file managers cache. This lead to\n   * compilation issues.\n   *\n   * It occurred when rendering two templates that share the same parent template.\n   */\n  @Test\n  void templateCachedButBytecodeCleared() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template1 = pebble.getTemplate(\"templates/template.parent.peb\");\n    PebbleTemplate template2 = pebble.getTemplate(\"templates/template.parent2.peb\");\n\n    Writer writer1 = new StringWriter();\n    Writer writer2 = new StringWriter();\n\n    template1.evaluate(writer1);\n    template2.evaluate(writer2);\n\n    assertEquals(\"GRANDFATHER TEXT ABOVE HEAD\" + LINE_SEPARATOR + LINE_SEPARATOR + \"\\tPARENT HEAD\"\n        + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"GRANDFATHER TEXT BELOW HEAD AND ABOVE FOOT\" + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"\\tGRANDFATHER FOOT\" + LINE_SEPARATOR + LINE_SEPARATOR\n        + \"GRANDFATHER TEXT BELOW FOOT\", writer1.toString());\n    assertEquals(\"GRANDFATHER TEXT ABOVE HEAD\" + LINE_SEPARATOR + LINE_SEPARATOR + \"\\tPARENT HEAD\"\n        + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"GRANDFATHER TEXT BELOW HEAD AND ABOVE FOOT\" + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"\\tGRANDFATHER FOOT\" + LINE_SEPARATOR + LINE_SEPARATOR\n        + \"GRANDFATHER TEXT BELOW FOOT\", writer2.toString());\n  }\n\n  @Test\n  void testConcurrentCacheHitting() throws InterruptedException, PebbleException {\n    final PebbleEngine engine = new PebbleEngine.Builder().strictVariables(false).build();\n\n    final ExecutorService es = Executors.newCachedThreadPool();\n    final AtomicInteger totalFailed = new AtomicInteger();\n\n    int numOfConcurrentThreads = Math.min(4, Runtime.getRuntime().availableProcessors());\n    final Semaphore semaphore = new Semaphore(numOfConcurrentThreads);\n\n    for (int i = 0; i < 100000; i++) {\n      semaphore.acquire();\n      es.submit(() -> {\n        try {\n          PebbleTemplate template = engine.getTemplate(\"templates/template.concurrent1.peb\");\n\n          int a = r.nextInt();\n          int b = r.nextInt();\n          int c = r.nextInt();\n\n          TestObject testObject = new TestObject(a, b, c);\n\n          StringWriter writer = new StringWriter();\n          Map<String, Object> context = new HashMap<>();\n          context.put(\"test\", testObject);\n          template.evaluate(writer, context);\n\n          String expectedResult = a + \":\" + b + \":\" + c;\n\n          String actualResult = writer.toString();\n          if (!expectedResult.equals(actualResult)) {\n            System.out.println(\"Expected: \" + expectedResult);\n            System.out.println(\"Actual: \" + actualResult);\n            totalFailed.incrementAndGet();\n          }\n\n        } catch (IOException | PebbleException e) {\n          e.printStackTrace();\n          totalFailed.incrementAndGet();\n        } finally {\n          semaphore.release();\n        }\n      });\n\n      // quick fail\n      if (totalFailed.intValue() > 0) {\n        break;\n      }\n    }\n    // Wait for them all to complete\n    semaphore.acquire(numOfConcurrentThreads);\n    es.shutdown();\n    assertEquals(0, totalFailed.intValue());\n  }\n\n  private static Random r = new SecureRandom();\n\n  public static class TestObject {\n\n    final public int a;\n    final public int b;\n    final public int c;\n\n    private TestObject(int a, int b, int c) {\n      this.a = a;\n      this.b = b;\n      this.c = c;\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/CompilerTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass CompilerTest {\n\n  @Test\n  void testCompile() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ foo }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"BAR\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello BAR\", writer.toString());\n  }\n\n  /**\n   * There was an issue where one failed template would prevent future templates from being\n   * compiled.\n   */\n  @Test\n  @Timeout(3)\n  void testCompilationMutexIsAlwaysReleased() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    try {\n      pebble.getTemplate(\"non-existing\");\n    } catch (Exception e) {\n\n    }\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.general.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"test\", writer.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ConcurrencyTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.security.SecureRandom;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Semaphore;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass ConcurrencyTest {\n\n  private static Random r = new SecureRandom();\n\n  public static class TestObject {\n\n    final public int a;\n\n    final public int b;\n\n    final public int c;\n\n    final public String d;\n\n    private TestObject(int a, int b, int c, String d) {\n      this.a = a;\n      this.b = b;\n      this.c = c;\n      this.d = d;\n    }\n  }\n\n  @Test\n  void testConcurrentEvaluation() throws InterruptedException, PebbleException {\n    PebbleEngine engine = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String templateSource = \"{{ test.a }}:{{ test.b }}:{{ test.c }}:{{ test.d | upper }}\";\n    final PebbleTemplate template = engine.getTemplate(templateSource);\n\n    ExecutorService es = Executors.newCachedThreadPool();\n    final AtomicInteger totalFailed = new AtomicInteger();\n\n    int numOfConcurrentEvaluations = Math.min(4, Runtime.getRuntime().availableProcessors());\n    final Semaphore semaphore = new Semaphore(numOfConcurrentEvaluations);\n\n    for (int i = 0; i < 10000; i++) {\n      semaphore.acquire();\n      es.submit(() -> {\n        try {\n\n          int a = r.nextInt();\n          int b = r.nextInt();\n          int c = r.nextInt();\n          int d = r.nextInt();\n\n          TestObject testObject = new TestObject(a, b, c, \"test\" + d);\n\n          StringWriter writer = new StringWriter();\n          Map<String, Object> context = new HashMap<>();\n          context.put(\"test\", testObject);\n          template.evaluate(writer, context);\n\n          String expectedResult = a + \":\" + b + \":\" + c + \":\" + \"TEST\" + d;\n\n          String actualResult = writer.toString();\n          if (!expectedResult.equals(actualResult)) {\n            System.out.println(\"Expected: \" + expectedResult);\n            System.out.println(\"Actual: \" + actualResult);\n            System.out.println();\n            totalFailed.incrementAndGet();\n          }\n\n        } catch (IOException | PebbleException e) {\n          e.printStackTrace();\n          totalFailed.incrementAndGet();\n        } finally {\n          semaphore.release();\n        }\n      });\n      if (totalFailed.intValue() > 0) {\n        break;\n      }\n    }\n    // Wait for them all to complete\n    semaphore.acquire(numOfConcurrentEvaluations);\n    es.shutdown();\n    assertEquals(0, totalFailed.intValue());\n  }\n\n  /**\n   * True concurrent compilation is not currently supported. The pebble engine will only compile one\n   * at a time. This test simply makes sure that if a user attempts to trigger concurrent\n   * compilation everything still succeeds (despite it being executed one at a time behind the\n   * scenes).\n   */\n  @Test\n  void testThreadSafeCompilationOfMultipleTemplates()\n      throws InterruptedException, PebbleException {\n    final PebbleEngine engine = new PebbleEngine.Builder().templateCache(null)\n        .strictVariables(false).build();\n    final ExecutorService es = Executors.newCachedThreadPool();\n    final AtomicInteger totalFailed = new AtomicInteger();\n\n    int numOfConcurrentEvaluations = Math.min(4, Runtime.getRuntime().availableProcessors());\n    final Semaphore semaphore = new Semaphore(numOfConcurrentEvaluations);\n\n    for (int i = 0; i < 1000; i++) {\n      semaphore.acquire(1);\n      es.submit(() -> {\n        try {\n          PebbleTemplate template = engine.getTemplate(\"templates/template.concurrent1.peb\");\n\n          int a = r.nextInt();\n          int b = r.nextInt();\n          int c = r.nextInt();\n          int d = r.nextInt();\n\n          TestObject testObject = new TestObject(a, b, c, \"test\" + d);\n\n          StringWriter writer = new StringWriter();\n          Map<String, Object> context = new HashMap<>();\n          context.put(\"test\", testObject);\n          template.evaluate(writer, context);\n\n          String expectedResult = a + \":\" + b + \":\" + c;\n\n          String actualResult = writer.toString();\n          if (!expectedResult.equals(actualResult)) {\n            System.out.println(\"Expected1: \" + expectedResult);\n            System.out.println(\"Actual1: \" + actualResult);\n            totalFailed.incrementAndGet();\n          }\n\n        } catch (IOException | PebbleException e) {\n          e.printStackTrace();\n          totalFailed.incrementAndGet();\n        } finally {\n          semaphore.release();\n        }\n      });\n      es.submit(() -> {\n        try {\n          PebbleTemplate template = engine.getTemplate(\"templates/template.concurrent2.peb\");\n\n          int a = r.nextInt();\n          int b = r.nextInt();\n          int c = r.nextInt();\n          int d = r.nextInt();\n\n          TestObject testObject = new TestObject(a, b, c, \"test\" + d);\n\n          StringWriter writer = new StringWriter();\n          Map<String, Object> context = new HashMap<>();\n          context.put(\"test\", testObject);\n          template.evaluate(writer, context);\n\n          String expectedResult = a + \":\" + b + \":\" + c;\n\n          String actualResult = writer.toString();\n          if (!expectedResult.equals(actualResult)) {\n            System.out.println(\"Expected2: \" + expectedResult);\n            System.out.println(\"Actual2: \" + actualResult);\n            totalFailed.incrementAndGet();\n          }\n\n        } catch (IOException | PebbleException e) {\n          e.printStackTrace();\n          totalFailed.incrementAndGet();\n        } finally {\n          semaphore.release();\n        }\n      });\n\n      if (totalFailed.intValue() > 0) {\n        break;\n      }\n    }\n    // Wait for them all to complete\n    semaphore.acquire(numOfConcurrentEvaluations);\n    es.shutdown();\n    assertEquals(0, totalFailed.intValue());\n  }\n\n  /**\n   * Issue #40\n   */\n  @Test\n  void testConcurrentEvaluationWithDifferingLocals()\n      throws InterruptedException, PebbleException {\n\n    final PebbleEngine engine = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    String templateSource = \"{{ 2000.234 | numberformat }}\";\n    final PebbleTemplate template = engine.getTemplate(templateSource);\n\n    ExecutorService es = Executors.newCachedThreadPool();\n    final AtomicInteger totalFailed = new AtomicInteger();\n\n    int numOfConcurrentEvaluations = Math.min(4, Runtime.getRuntime().availableProcessors());\n    final Semaphore semaphore = new Semaphore(numOfConcurrentEvaluations);\n\n    final String germanResult = \"2.000,234\";\n    final String canadianResult = \"2,000.234\";\n\n    for (int i = 0; i < 1000; i++) {\n      semaphore.acquire();\n      es.submit(() -> {\n        try {\n\n          final boolean isGerman = r.nextBoolean();\n\n          final Locale locale = isGerman ? Locale.GERMANY : Locale.CANADA;\n\n          final StringWriter writer = new StringWriter();\n\n          template.evaluate(writer, locale);\n\n          final String expectedResult = isGerman ? germanResult : canadianResult;\n\n          final String actualResult = writer.toString();\n\n          if (!expectedResult.equals(actualResult)) {\n            System.out.println(String.format(\"Locale: %s\\nExpected: %s\\nActual: %s\\n\",\n                locale.toString(), expectedResult, actualResult));\n            totalFailed.incrementAndGet();\n          }\n\n        } catch (IOException | PebbleException e) {\n          e.printStackTrace();\n          totalFailed.incrementAndGet();\n        } finally {\n          semaphore.release();\n        }\n      });\n      if (totalFailed.intValue() > 0) {\n        break;\n      }\n    }\n    // Wait for them all to complete\n    semaphore.acquire(numOfConcurrentEvaluations);\n    es.shutdown();\n    assertEquals(0, totalFailed.intValue());\n  }\n\n  @Test\n  void testConcurrentEvaluationWithImportingMacros() throws PebbleException, IOException {\n    Loader<String> loader = new Loader<String>() {\n      private final String TEMPLATE =\n          \"{% import \\\"macro\\\" %}\"\n              + \"{{test(0)}}\"\n              + \"{% for i in [1,2,3,4,5,6,7,8,9,10] %}\"\n              + \"    {% parallel %}\"\n              + \"        {% import \\\"macro\\\" %}\"\n              + \"        {{test(i)}}\"\n              + \"    {% endparallel %}\"\n              + \"{% endfor %}\";\n\n      private final String MACRO =\n          \"{% macro test(count) %}\"\n              + \"    test({{count}})\"\n              + \"{% endmacro %}\";\n\n      @Override\n      public Reader getReader(String cacheKey) throws LoaderException {\n        switch (cacheKey) {\n          case \"template\":\n            return new StringReader(this.TEMPLATE);\n          case \"macro\":\n            return new StringReader(this.MACRO);\n          default:\n            throw new IllegalStateException(\"No such file\");\n        }\n      }\n\n      @Override\n      public void setCharset(String charset) {\n        throw new UnsupportedOperationException(\n            \"Not supported yet.\"); //To change body of generated methods, choose Tools | Templates.\n      }\n\n      @Override\n      public void setPrefix(String prefix) {\n        throw new UnsupportedOperationException(\n            \"Not supported yet.\"); //To change body of generated methods, choose Tools | Templates.\n      }\n\n      @Override\n      public void setSuffix(String suffix) {\n        throw new UnsupportedOperationException(\n            \"Not supported yet.\"); //To change body of generated methods, choose Tools | Templates.\n      }\n\n      @Override\n      public String resolveRelativePath(String relativePath, String anchorPath) {\n        return relativePath;\n      }\n\n      @Override\n      public String createCacheKey(String templateName) {\n        return templateName;\n      }\n\n      @Override\n      public boolean resourceExists(String templateName) {\n        return true;\n      }\n    };\n\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n\n    PebbleTemplate template = engine.getTemplate(\"template\");\n\n    StringWriter singleThreadResult = new StringWriter();\n\n    template.evaluate(singleThreadResult);\n\n    ExecutorService executor = Executors.newCachedThreadPool();\n\n    engine = new PebbleEngine.Builder().loader(loader).executorService(executor)\n        .strictVariables(false).build();\n\n    template = engine.getTemplate(\"template\");\n\n    StringWriter multipleThreadResult = new StringWriter();\n\n    template.evaluate(multipleThreadResult);\n\n    executor.shutdown();\n\n    assertEquals(singleThreadResult.toString(), multipleThreadResult.toString());\n  }\n\n  @Test\n  void testConcurrentEvaluationWithException() throws PebbleException {\n    PebbleEngine engine = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String templateSource =\n        \"{% for i in [1,2,3,4,5,6,7,8,9,10] %}\"\n            + \"{% parallel %}\"\n            + \"{{test(i)}}\"\n            + \"{% endparallel %}\"\n            + \"{% endfor %}\";\n\n    PebbleTemplate template = engine.getTemplate(templateSource);\n\n    StringWriter singleThreadResult = new StringWriter();\n\n    try {\n      template.evaluate(singleThreadResult);\n      fail(\"Expecting the single thread evaluation to throw an exception\");\n    } catch (Exception ex) {\n    }\n\n    ExecutorService executor = Executors.newCachedThreadPool();\n\n    engine = new PebbleEngine.Builder().loader(new StringLoader()).executorService(executor)\n        .strictVariables(false).build();\n\n    template = engine.getTemplate(templateSource);\n\n    StringWriter multipleThreadResult = new StringWriter();\n\n    try {\n      template.evaluate(multipleThreadResult);\n      fail(\"Expection the multi thread evaluation to throw an exception\");\n    } catch (Exception ex) {\n    }\n\n    executor.shutdown();\n\n    assertEquals(singleThreadResult.toString(), multipleThreadResult.toString(), \"Expection the result of multiple threads and single thread execution to match.\");\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ContextTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.error.RootAttributeNotFoundException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass ContextTest {\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testLazyMap() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ eager_key }} {{ lazy_key }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<String, Object>() {\n\n      {\n        this.put(\"eager_key\", \"eager_value\");\n      }\n\n      @Override\n      public Object get(final Object key) {\n        if (\"lazy_key\".equals(key)) {\n          return \"lazy_value\";\n        }\n        return super.get(key);\n      }\n\n      @Override\n      public boolean containsKey(Object key) {\n        if (\"lazy_key\".equals(key)) {\n          return true;\n        }\n        return super.containsKey(key);\n      }\n    });\n    assertEquals(\"eager_value lazy_value\", writer.toString());\n  }\n\n  @Test\n  void testMissingContextVariableWithoutStrictVariables()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ foo }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testMissingContextVariableWithStrictVariables() throws PebbleException, IOException {\n    assertThrows(RootAttributeNotFoundException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ foo }}\");\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n    });\n  }\n\n  @Test\n  void testExistingButNullContextVariableWithStrictVariables()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% if foo == null %}YES{% endif %}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"YES\", writer.toString());\n  }\n\n  @Test\n  void testDefaultLocale() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.CANADA_FRENCH).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ locale.language }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"fr\", writer.toString());\n  }\n\n  @Test\n  void testLocaleProvidedDuringEvaluation() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.CANADA).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ locale }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, Locale.CANADA);\n    assertEquals(\"en_CA\", writer.toString());\n  }\n\n  @Test\n  void testGlobalTemplateName() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ template.name }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"{{ template.name }}\", writer.toString());\n  }\n\n  @Test\n  void testImmutableMapThrows() throws PebbleException, IOException {\n    assertThrows(UnsupportedOperationException.class, () -> {\n      Map<String, Object> originalMap = new HashMap<>();\n      originalMap.put(\"contextVariable\", \"context variable value\");\n      Map<String, Object> immutableMap = new ImmutableMap<>(originalMap);\n\n      immutableMap.put(\"templateVariable\", \"template variable value\");\n    });\n  }\n\n  @Test\n  void testImmutableContext() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine\n        .Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .build();\n    PebbleTemplate template = pebble.getTemplate(\"\" +\n        \"{% set templateVariable = 'template variable value' %}\" +\n        \"{{ contextVariable }} - {{ templateVariable }}\");\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> originalMap = new HashMap<>();\n    originalMap.put(\"contextVariable\", \"context variable value\");\n\n    template.evaluate(writer, new ImmutableMap<>(originalMap));\n    assertEquals(\"context variable value - template variable value\", writer.toString());\n  }\n\n  private static class ImmutableMap<T, U> implements Map<T, U> {\n\n    private final Map<T, U> delegate;\n\n    private ImmutableMap(Map<T, U> delegate) {\n      this.delegate = delegate;\n    }\n\n    @Override\n    public int size() {\n      return this.delegate.size();\n    }\n\n    @Override\n    public boolean isEmpty() {\n      return this.delegate.isEmpty();\n    }\n\n    @Override\n    public boolean containsKey(Object key) {\n      return this.delegate.containsKey(key);\n    }\n\n    @Override\n    public boolean containsValue(Object value) {\n      return this.delegate.containsValue(value);\n    }\n\n    @Override\n    public U get(Object key) {\n      return this.delegate.get(key);\n    }\n\n    @Override\n    public U put(T key, U value) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public U remove(Object key) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public void putAll(Map<? extends T, ? extends U> m) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public void clear() {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public Set<T> keySet() {\n      return this.delegate.keySet();\n    }\n\n    @Override\n    public Collection<U> values() {\n      return this.delegate.values();\n    }\n\n    @Override\n    public Set<Entry<T, U>> entrySet() {\n      return this.delegate.entrySet();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n      return this.delegate.equals(o);\n    }\n\n    @Override\n    public int hashCode() {\n      return this.delegate.hashCode();\n    }\n\n    @Override\n    public U getOrDefault(Object key, U defaultValue) {\n      return this.delegate.getOrDefault(key, defaultValue);\n    }\n\n    @Override\n    public void forEach(BiConsumer<? super T, ? super U> action) {\n      this.delegate.forEach(action);\n    }\n\n    @Override\n    public void replaceAll(BiFunction<? super T, ? super U, ? extends U> function) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public U putIfAbsent(T key, U value) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public boolean remove(Object key, Object value) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public boolean replace(T key, U oldValue, U newValue) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public U replace(T key, U value) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public U computeIfAbsent(T key, Function<? super T, ? extends U> mappingFunction) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public U computeIfPresent(T key, BiFunction<? super T, ? super U, ? extends U> remappingFunction) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public U compute(T key, BiFunction<? super T, ? super U, ? extends U> remappingFunction) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n\n    @Override\n    public U merge(T key, U value, BiFunction<? super U, ? super U, ? extends U> remappingFunction) {\n      throw new UnsupportedOperationException(\"this map is immutable\");\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/CoreFiltersTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.TestingExtension;\nimport io.pebbletemplates.pebble.extension.core.*;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.time.*;\nimport java.util.*;\n\nimport static java.lang.Boolean.TRUE;\nimport static java.util.Collections.emptyList;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass CoreFiltersTest {\n\n  @Test\n  void testAbs() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ -5 | abs }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"5\", writer.toString());\n  }\n\n  @Test\n  void testAbsDouble() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ -5.2 | abs }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"5.2\", writer.toString());\n  }\n\n  @Test\n  void testChainedFiltersWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | upper | lower }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testLower() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 'TEMPLATE' | lower }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"template\", writer.toString());\n  }\n\n  @Test\n  void testLowerWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | lower }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testUpper() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 'template' | upper }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"TEMPLATE\", writer.toString());\n  }\n\n  @Test\n  void testUpperWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | upper }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testDate() throws ParseException, PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.ENGLISH).build();\n\n    String source = \"{{ realDate | date('MM/dd/yyyy') }}{{ realDate | date(format) }}{{ stringDate | date('yyyy/MMMM/d','yyyy-MMMM-d') }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    DateFormat format = new SimpleDateFormat(\"yyyy-MMMM-d\", Locale.ENGLISH);\n    Date realDate = format.parse(\"2012-July-01\");\n    context.put(\"realDate\", realDate);\n    context.put(\"stringDate\", format.format(realDate));\n    context.put(\"format\", \"yyyy-MMMM-d\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"07/01/20122012-July-12012/July/1\", writer.toString());\n  }\n\n  @Test\n  void testStringDateWithOnlyFormat() throws PebbleException, IOException{\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false)\n            .defaultLocale(Locale.ENGLISH).build();\n\n    String source = \"{{ stringDate | date('MM/dd/yyyy') }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"stringDate\", \"2004-02-12T15:19:21+0400\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"02/12/2004\", writer.toString());\n  }\n\n  @Test\n  void testStringDateWithoutFormat() throws PebbleException, IOException{\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false)\n            .defaultLocale(Locale.ENGLISH).build();\n\n    String source = \"{{ stringDate | date }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"stringDate\", \"2004-02-12T15:19:21+0400\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertTrue(writer.toString().startsWith(\"2004-02-12T\"));\n  }\n\n  @Test\n  void testStringDateWithoutFormatAndNotISO_DATE() throws PebbleException, IOException{\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false)\n            .defaultLocale(Locale.ENGLISH).build();\n\n    String source = \"{{ stringDate | date }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"stringDate\", \"2004-02-12\");\n\n    Writer writer = new StringWriter();\n    PebbleException exception = assertThrows(PebbleException.class, () -> template.evaluate(writer, context));\n    assertEquals(exception.getMessage(), \"Could not parse the string '2004-02-12' into a date, with formatting: yyyy-MM-dd'T'HH:mm:ssZ ({{ stringDate | date }}:1)\");\n  }\n\n  @Test\n  void testDateJava8() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine\n        .Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.ENGLISH)\n        .build();\n\n    final LocalDateTime localDateTime = LocalDateTime.of(2017, 6, 30, 13, 30, 35, 0);\n    final ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of(\"GMT+0100\"));\n    final LocalDate localDate = localDateTime.toLocalDate();\n    final LocalTime localTime = localDateTime.toLocalTime();\n\n    StringBuilder source = new StringBuilder();\n    source\n        .append(\"{{ localDateTime | date }}\")\n        .append(\"{{ localDateTime | date('yyyy-MM-dd HH:mm:ss') }}\")\n        .append(\"{{ zonedDateTime | date('yyyy-MM-dd HH:mm:ssXXX') }}\")\n        .append(\"{{ localDate | date('yyyy-MM-dd') }}\")\n        .append(\"{{ localTime | date('HH:mm:ss') }}\");\n\n    PebbleTemplate template = pebble.getTemplate(source.toString());\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"localDateTime\", localDateTime);\n    context.put(\"zonedDateTime\", zonedDateTime);\n    context.put(\"localDate\", localDate);\n    context.put(\"localTime\", localTime);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\n        \"2017-06-30T13:30:352017-06-30 13:30:352017-06-30 13:30:35+01:002017-06-3013:30:35\",\n        writer.toString());\n  }\n\n\n  @Test\n  void testDateWithNamedArguments() throws ParseException, PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.ENGLISH).build();\n\n    String source = \"{{ stringDate | date(existingFormat='yyyy-MMMM-d', format='yyyy/MMMM/d') }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    DateFormat format = new SimpleDateFormat(\"yyyy-MMMM-d\", Locale.ENGLISH);\n    Date realDate = format.parse(\"2012-July-01\");\n    context.put(\"stringDate\", format.format(realDate));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"2012/July/1\", writer.toString());\n  }\n\n  @Test\n  void testDateWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ null | date(\\\"MM/dd/yyyy\\\") }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testDateWithNumberInput() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ dateAsNumber | date(\\\"MM/dd/yyyy\\\") }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"dateAsNumber\", 1518004210000L);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"02/07/2018\", writer.toString());\n  }\n\n  @Test\n  void testDateWithDateAndExplicitTimeZone() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ date | date(timeZone=\\\"Asia/Almaty\\\") }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"date\", Date.from(Instant.ofEpochSecond(1595853935)));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"2020-07-27T18:45:35+0600\", writer.toString());\n  }\n\n  @Test\n  void testDateWithDateAndFormatAndExplicitTimeZone() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ date | date(\\\"yyyy-MM-dd'T'HH:mm:ssX\\\", timeZone=\\\"Asia/Almaty\\\") }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"date\", Date.from(Instant.ofEpochSecond(1595853935)));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"2020-07-27T18:45:35+06\", writer.toString());\n  }\n\n  @Test\n  void testDateWithTimestampAndExplicitTimeZone() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ timestamp | date(timeZone=\\\"Asia/Almaty\\\") }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"timestamp\", 1595853935000L);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"2020-07-27T18:45:35+0600\", writer.toString());\n  }\n\n  @Test\n  void testDateWithOffsetDateTimeAndExplicitTimeZoneUsesTimeZoneOfInput() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ offsetDateTime | date(timeZone=\\\"Asia/Almaty\\\") }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"offsetDateTime\", OffsetDateTime.of(2020, 7, 27, 16, 12, 13, 0, ZoneOffset.ofHours(3)));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"2020-07-27T16:12:13+03:00\", writer.toString());\n  }\n\n  @Test\n  void testDateWithOffsetDateTimeAndFormatAndExplicitTimeZoneUsesTimeZoneOfInput() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ offsetDateTime | date(\\\"yyyy-MM-dd'T'HH:mm:ssX\\\", timeZone=\\\"Asia/Almaty\\\") }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"offsetDateTime\", OffsetDateTime.of(2020, 7, 27, 16, 12, 13, 0, ZoneOffset.ofHours(3)));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"2020-07-27T16:12:13+03\", writer.toString());\n  }\n\n  @Test\n  void testDateWithOffsetDateTimeAndFormatAndNoExplicitTimeZoneUsesTimeZoneOfInput() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ offsetDateTime | date(\\\"yyyy-MM-dd'T'HH:mm:ssX\\\") }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"offsetDateTime\", OffsetDateTime.of(2020, 7, 27, 16, 12, 13, 0, ZoneOffset.ofHours(5)));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"2020-07-27T16:12:13+05\", writer.toString());\n  }\n\n  @Test\n  void testDateWithInstantAndExplicitTimeZone() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ instant | date(timeZone=\\\"Asia/Almaty\\\") }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"instant\", Instant.ofEpochSecond(1595853935));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"2020-07-27T18:45:35+06:00[Asia/Almaty]\", writer.toString());\n  }\n\n  @Test\n  void testDateWithInstantAndNoExplicitTimeZoneUsesSystemTimeZone() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .build();\n\n    TimeZone defaultTimeZone = TimeZone.getDefault();\n    try {\n      TimeZone.setDefault(TimeZone.getTimeZone(\"Pacific/Funafuti\"));\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ instant | date() }}\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"instant\", Instant.ofEpochSecond(1595853935));\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n\n      assertEquals(\"2020-07-28T00:45:35+12:00[Pacific/Funafuti]\", writer.toString());\n    }\n    finally {\n      TimeZone.setDefault(defaultTimeZone);\n    }\n  }\n\n  @Test\n  void testDateWithUnsupportedInput() throws IOException {\n    assertThrows(IllegalArgumentException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ unsupportedDateType | date(\\\"MM/dd/yyyy\\\") }}\";\n\n      PebbleTemplate template = pebble.getTemplate(source);\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"unsupportedDateType\", TRUE);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testUrlEncode() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 'The string ü@foo-bar' | urlencode }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"The+string+%C3%BC%40foo-bar\", writer.toString());\n  }\n\n  @Test\n  void testUrlEncodeWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | urlencode }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testNumberFormatFilterWithFormat() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.ENGLISH).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"You owe me {{ 10000.235166 | numberformat(currencyFormat) }}.\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"currencyFormat\", \"$#,###,###,##0.00\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"You owe me $10,000.24.\", writer.toString());\n  }\n\n  @Test\n  void testNumberFormatFilterWithNamedArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.US).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"You owe me {{ 10000.235166 | numberformat(format=currencyFormat) }}.\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"currencyFormat\", \"$#,###,###,##0.00\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"You owe me $10,000.24.\", writer.toString());\n  }\n\n  @Test\n  void testNumberFormatFilterWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | numberformat(currencyFormat) }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testNumberFormatFilterWithLocale() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.ENGLISH).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 1000000 | numberformat }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"1,000,000\", writer.toString());\n  }\n\n  @Test\n  void testAbbreviate() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ 'This is a test of the abbreviate filter' | abbreviate(16) }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"This is a tes...\", writer.toString());\n  }\n\n  @Test\n  void testAbbreviateWithNamedArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ 'This is a test of the abbreviate filter' | abbreviate(length=16) }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"This is a tes...\", writer.toString());\n  }\n\n  @Test\n  void testAbbreviateWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | abbreviate(16) }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testAbbreviateWithSmallLength() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | abbreviate(2)}}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"1234567\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"12\", writer.toString());\n  }\n\n  @Test\n  void testCapitalize() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ 'this should be capitalized.' | capitalize }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"This should be capitalized.\", writer.toString());\n  }\n\n  @Test\n  void testCapitalizeWithLeadingWhitespace() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ ' \\nthis should be capitalized.' | capitalize }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\" \\nThis should be capitalized.\", writer.toString());\n  }\n\n  @Test\n  void testCapitalizeWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | capitalize }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testCapitalizeWithEmptyString() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ '' | capitalize }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testSortFilter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% for word in words|sort %}{{ word }} {% endfor %}\");\n    List<String> words = new ArrayList<>();\n    words.add(\"zebra\");\n    words.add(\"apple\");\n    words.add(\" cat\");\n    words.add(\"123\");\n    words.add(\"Apple\");\n    words.add(\"cat\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"words\", words);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\" cat 123 Apple apple cat zebra \", writer.toString());\n  }\n\n  @Test\n  void testRsortFilter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% for word in words|rsort %}{{ word }} {% endfor %}\");\n    List<String> words = new ArrayList<>();\n    words.add(\"zebra\");\n    words.add(\"apple\");\n    words.add(\" cat\");\n    words.add(\"123\");\n    words.add(\"Apple\");\n    words.add(\"cat\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"words\", words);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"zebra cat apple Apple 123  cat \", writer.toString());\n  }\n\n  @Test\n  void testReverseFilter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% for word in words|reverse %}{{ word }} {% endfor %}\");\n    List<String> words = new ArrayList<>();\n    words.add(\"one\");\n    words.add(\"two\");\n    words.add(\"three\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"words\", words);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"three two one \", writer.toString());\n  }\n\n  @Test\n  void testTitle() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ null | title }} {{ 'test' | title }} {{ 'test test' | title }} {{ 'TEST TEST' | title }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\" Test Test Test TEST TEST\", writer.toString());\n  }\n\n  @Test\n  void testTitleWithLeadingWhitespace() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ ' \\ntest' | title }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\" \\nTest\", writer.toString());\n  }\n\n  @Test\n  void testTrim() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ '        \t\tThis should be trimmed. \t\t' | trim }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"This should be trimmed.\", writer.toString());\n  }\n\n  @Test\n  void testTrimWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | trim }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testDefault() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ obj|default('ONE') }} {{ null|default('TWO') }} {{ '  ' |default('THREE') }} {{ 4 |default('FOUR') }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"ONE TWO THREE 4\", writer.toString());\n  }\n\n  /**\n   * Tests if the {@link DefaultFilter} is working as\n   * expected.\n   *\n   * @throws Exception thrown when something went wrong.\n   */\n  @Test\n  void testDefaultFilterWithStrictMode() throws Exception {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ name | default('test') }}\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"test\", writer.toString());\n\n  }\n\n  @Test\n  void testDefaultWithNamedArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ obj|default(default='ONE') }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"ONE\", writer.toString());\n  }\n\n  @Test\n  void testFirst() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | first }}\");\n\n    List<String> names = new ArrayList<>();\n    names.add(\"Alex\");\n    names.add(\"Joe\");\n    names.add(\"Bob\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Alex\", writer.toString());\n  }\n\n  @Test\n  void testFirstWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | first }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testFirstWithStringInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ name | first }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"Alex\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"A\", writer.toString());\n  }\n\n  @Test\n  void testFirstWithEmptyCollection() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | first }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", emptyList());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testJoin() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | join(',') }}\");\n\n    List<String> names = new ArrayList<>();\n    names.add(\"Alex\");\n    names.add(\"Joe\");\n    names.add(\"Bob\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Alex,Joe,Bob\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithoutGlue() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | join }}\");\n\n    List<String> names = new ArrayList<>();\n    names.add(\"Alex\");\n    names.add(\"Joe\");\n    names.add(\"Bob\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"AlexJoeBob\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithNumbers() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ numbers | join(',') }}\");\n\n    List<Integer> numbers = new ArrayList<>();\n    numbers.add(1);\n    numbers.add(2);\n    numbers.add(3);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"numbers\", numbers);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"1,2,3\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | join(',') }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithStringArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | join(',') }}\");\n\n    String[] names = new String[]{\"Alex\", \"Joe\", \"Bob\"};\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Alex,Joe,Bob\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithStringArrayWithoutGlue() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | join }}\");\n\n    String[] names = new String[]{\"Alex\", \"Joe\", \"Bob\"};\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"AlexJoeBob\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithNumbersArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ numbers | join(',') }}\");\n\n    int[] numbers = new int[]{1, 2, 3};\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"numbers\", numbers);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"1,2,3\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithEmptyNumbersArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ numbers | join(',') }}\");\n\n    int[] numbers = new int[0];\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"numbers\", numbers);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testJoinWithFloatArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ numbers | join(',') }}\");\n\n    float[] numbers = new float[]{1.0f, 2.5f, 3.0f};\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"numbers\", numbers);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"1.0,2.5,3.0\", writer.toString());\n  }\n\n  @Test\n  void testLast() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | last }}\");\n\n    List<String> names = new ArrayList<>();\n    names.add(\"Alex\");\n    names.add(\"Joe\");\n    names.add(\"Bob\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @Test\n  void testLastWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | last }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testLastWithStringInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ name | last }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"Alex\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"x\", writer.toString());\n  }\n\n  @Test\n  void testLastWithArrayInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | last }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", new String[]{\"FirstName\", \"FamilyName\"});\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"FamilyName\", writer.toString());\n  }\n\n  @Test\n  void testLastWithPrimitiveArrayInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ ages | last }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"ages\", new int[]{28, 30});\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"30\", writer.toString());\n  }\n\n  @Test\n  void testFirstWithArrayInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | first }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", new String[]{\"FirstName\", \"FamilyName\"});\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"FirstName\", writer.toString());\n  }\n\n  @Test\n  void testFirstWithPrimitiveArrayInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ ages | first }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"ages\", new int[]{28, 30});\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"28\", writer.toString());\n  }\n\n  public class User {\n\n    private final String username;\n\n    public User(String username) {\n      this.username = username;\n    }\n\n    public String getUsername() {\n      return this.username;\n    }\n  }\n\n  @Test\n  void testSliceWithNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | slice }}\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testSliceWithDefaultArgs() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ name | slice }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"Alex\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Alex\", writer.toString());\n  }\n\n  @Test\n  void testSliceWithInvalidFirstArg() throws PebbleException, IOException {\n    assertThrows(IllegalArgumentException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ name | slice(-1) }}\");\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"name\", \"Alex\");\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testSliceWithIntegerArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ 'abcdefghijklmnopqrstuvwxyz' | slice(from, to) }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"from\", new Integer(2));\n    context.put(\"to\", new Integer(4));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"cd\", writer.toString());\n  }\n\n  @Test\n  void testSliceWithInvalidSecondArg() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ name | slice(0,-1) }}\");\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"name\", \"Alex\");\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testSliceWithInvalidSecondArg2() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ name | slice(0,1000) }}\");\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"name\", \"Alex\");\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testSliceWithString() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ name | slice(2,5) }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"Alexander\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"exa\", writer.toString());\n  }\n\n  @Test\n  void testSliceWithList() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | slice(2,5) }}\");\n\n    List<String> names = new ArrayList<>();\n    names.add(\"Alex\");\n    names.add(\"Joe\");\n    names.add(\"Bob\");\n    names.add(\"Sarah\");\n    names.add(\"Mary\");\n    names.add(\"Marge\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"[Bob, Sarah, Mary]\", writer.toString());\n  }\n\n  @Test\n  void testSliceWithStringArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% set n = names | slice(2,5) %}{{ n[0] }}\");\n\n    String[] names = new String[]{\"Alex\", \"Joe\", \"Bob\", \"Sarah\", \"Mary\", \"Marge\"};\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", names);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Bob\", writer.toString());\n  }\n\n  @Test\n  void testSliceWithPrimitivesArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% set p = primitives | slice(2,5) %}{{ p[0] }}\");\n    Map<String, Object> context = new HashMap<>();\n    Writer writer;\n\n    // boolean\n    writer = new StringWriter();\n    boolean[] booleans = new boolean[]{true, false, true, false, true, false};\n    context.put(\"primitives\", booleans);\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n\n    // byte\n    writer = new StringWriter();\n    byte[] bytes = new byte[]{0, 1, 2, 3, 4, 5};\n    context.put(\"primitives\", bytes);\n    template.evaluate(writer, context);\n    assertEquals(\"2\", writer.toString());\n\n    // char\n    writer = new StringWriter();\n    char[] chars = new char[]{'a', 'b', 'c', 'd', 'e', 'f'};\n    context.put(\"primitives\", chars);\n    template.evaluate(writer, context);\n    assertEquals(\"c\", writer.toString());\n\n    // double\n    writer = new StringWriter();\n    double[] doubles = new double[]{0.0d, 1.0d, 2.0d, 3.0d, 4.0d, 5.0d};\n    context.put(\"primitives\", doubles);\n    template.evaluate(writer, context);\n    assertEquals(\"2.0\", writer.toString());\n\n    // float\n    writer = new StringWriter();\n    float[] floats = new float[]{0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f};\n    context.put(\"primitives\", floats);\n    template.evaluate(writer, context);\n    assertEquals(\"2.0\", writer.toString());\n\n    // int\n    writer = new StringWriter();\n    int[] ints = new int[]{0, 1, 2, 3, 4, 5};\n    context.put(\"primitives\", ints);\n    template.evaluate(writer, context);\n    assertEquals(\"2\", writer.toString());\n\n    // long\n    writer = new StringWriter();\n    long[] longs = new long[]{0, 1, 2, 3, 4, 5};\n    context.put(\"primitives\", longs);\n    template.evaluate(writer, context);\n    assertEquals(\"2\", writer.toString());\n\n    // short\n    writer = new StringWriter();\n    short[] shorts = new short[]{0, 1, 2, 3, 4, 5};\n    context.put(\"primitives\", shorts);\n    template.evaluate(writer, context);\n    assertEquals(\"2\", writer.toString());\n  }\n\n  @Test\n  void testSliceWithInvalidInputType() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ names | slice(2,5) }}\");\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"names\", Long.valueOf(1));\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  /**\n   * Tests {@link LengthFilter} with different inputs.\n   */\n  @Test\n  void testLengthFilterInputs() {\n    LengthFilter filter = new LengthFilter();\n\n    assertEquals(0, filter.apply(null, null, null, null, 0));\n    assertEquals(4, filter.apply(\"test\", null, null, null, 0));\n    assertEquals(0, filter.apply(Collections.EMPTY_LIST, null, null, null, 0));\n    assertEquals(2, filter.apply(Arrays.asList(\"tttt\", \"ssss\"), null, null, null, 0));\n    assertEquals(2, filter.apply(Arrays.asList(\"tttt\", \"ssss\").iterator(), null, null, null, 0));\n    Map<String, String> test = new HashMap<>();\n    test.put(\"test\", \"test\");\n    test.put(\"other\", \"other\");\n    test.put(\"and_other\", \"other\");\n    assertEquals(3, filter.apply(test, null, null, null, 0));\n  }\n\n  /**\n   * Tests {@link LengthFilter} if the length filter is working within templates.\n   */\n  @Test\n  void testLengthFilterInTemplate() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ names | length }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", \"test\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"4\", writer.toString());\n  }\n\n  /**\n   * Tests {@link ReplaceFilter} if it can handle a null input.\n   */\n  @Test\n  void testReplaceFilterNullInput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n            .getTemplate(\n                    \"{{ null |replace({'%this%': foo, '%that%': \\\"bar\\\"}) }}\");\n\n    Writer writer = new StringWriter();\n    assertDoesNotThrow(() -> template.evaluate(writer, new HashMap<>()));\n  }\n\n  /**\n   * Tests {@link ReplaceFilter} if the length filter is working within templates.\n   */\n  @Test\n  void testReplaceFilterInTemplate() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\n            \"{{ \\\"I like %this% and %that%.\\\"|replace({'%this%': foo, '%that%': \\\"bar\\\"}) }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"foo\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"I like foo and bar.\", writer.toString());\n  }\n\n  /**\n   * Tests {@link Base64EncoderFilter} if the base64 encoding filter is working for a string value, a string constant, null.\n   */\n  @Test\n  void testBase64EncoderFilterInTemplate() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"var=\\\"{{ var | base64encode }}\\\" const=\\\"{{ \\\"test\\\" | base64encode }}\\\" null=\\\"{{ null | base64encode }}\\\"\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", \"test\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"var=\\\"dGVzdA==\\\" const=\\\"dGVzdA==\\\" null=\\\"\\\"\", writer.toString());\n  }\n\n  /**\n   * Tests {@link Base64DecoderFilter} if the base64 decoder filter is working for a string value, a string constant, null.\n   */\n  @Test\n  void testBase64DecoderFilterInTemplate() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"var=\\\"{{ var | base64decode }}\\\" const=\\\"{{ \\\"dGVzdA==\\\" | base64decode }}\\\" null=\\\"{{ null | base64decode }}\\\"\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", \"dGVzdA==\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"var=\\\"test\\\" const=\\\"test\\\" null=\\\"\\\"\", writer.toString());\n  }\n\n  @Test\n  void testBase64DecodeFilterBadEncodedStringFail() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n              .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ \\\"this is not a base64 encoded string\\\" | base64decode }}\");\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testBase64DecodeFilterNoStringFail() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n              .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ {'foo':1} | base64decode }}\");\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testSha256FilterNoStringFail() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n              .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ {'foo':1} | sha256 }}\");\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  /**\n   * Tests {@link Sha256Filter} if the SHA256 hashing filter is working for a string value, a string constant, null.\n   */\n  @Test\n  void testSha256FilterInTemplate() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"var=\\\"{{ var | sha256 }}\\\" const=\\\"{{ \\\"test\\\" | sha256}}\\\" null=\\\"{{ null | sha256 }}\\\"\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", \"test\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"var=\\\"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\\\" const=\\\"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\\\" null=\\\"\\\"\", writer.toString());\n  }\n\n  @Test\n  void testMergeOk() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .extension(new TestingExtension()).strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\n            \"{{{'one':1}|merge({'two':2})|mapToString}} {%set m1 = {'one':1}|merge(['two'])%}{{m1['two']}} {{[1]|merge([2])|listToString}} {%set l1 = [1]|merge({'two':2})%}{{l1[1].value}} {{arr1|merge(arr2)|arrayToString}}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"arr1\", new int[]{1});\n    context.put(\"arr2\", new int[]{2});\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"{one=1, two=2} two [1,2] 2 [1,2]\", writer.toString());\n  }\n\n  @Test\n  void testMergeMapWithStringAndFail() throws PebbleException, IOException {\n    assertThrows(UnsupportedOperationException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ {'one':1}|merge('No way!') }}\");\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testMergeListWithStringAndFail() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ [1]|merge('No way!') }}\");\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testMergeDifferentArraysAndFail() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ arr1|merge(arr2) }}\");\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"arr1\", new int[]{1});\n      context.put(\"arr2\", new String[]{\"2\"});\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/CoreFunctionsTest.java",
    "content": "/**\n * **************************************************************************** This file is part of\n * Pebble.\n * <p/>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p/>\n * For the full copyright and license information, please view the LICENSE file that was distributed\n * with this source code. ****************************************************************************\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass CoreFunctionsTest {\n\n  public static final String LINE_SEPARATOR = System.lineSeparator();\n\n  @Test\n  void testBlockFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/function/template.block.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"Default Title\" + LINE_SEPARATOR + \"Default Title\", writer.toString());\n  }\n\n  @Test\n  void testParentFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/function/template.child.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\n        \"parent text\" + LINE_SEPARATOR + \"\\t\\tparent head\" + LINE_SEPARATOR + \"\\tchild head\"\n            + LINE_SEPARATOR, writer.toString());\n  }\n\n  /**\n   * Issue occurred where parent block didn't have access to the context when invoked via the\n   * parent() function.\n   */\n  @Test\n  void testParentBlockHasAccessToContext() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/function/template.childWithContext.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"bar\", writer.toString());\n  }\n\n  @Test\n  void testParentThenMacro() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/function/template.childThenParentThenMacro.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"test\", writer.toString());\n  }\n\n  /**\n   * Two levels of parent functions would cause a stack overflow error, #61.\n   */\n  @Test\n  void testParentFunctionWithTwoLevels() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/function/template.subchild.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\n        \"parent text\" + LINE_SEPARATOR + \"\\t\\t\\tparent head\" + LINE_SEPARATOR + \"\\tchild head\"\n            + LINE_SEPARATOR + \"\\tsub child head\" + LINE_SEPARATOR, writer.toString());\n  }\n\n  @Test\n  void testMinFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ min(8.0, 1, 4, 5, object.large) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"1\", writer.toString());\n  }\n\n  @Test\n  void testMaxFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ max(8.0, 1, 4, 5, object.large) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"20\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(0,5) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"012345\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionIncrement2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(0,10,2) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"0246810\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionDecrement2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(10,0,-2) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"1086420\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionIncrement0() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% for i in range(0,5,0) %}{{ i }}{% endfor %}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testRangeFunctionChar() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range('a','e') %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"abcde\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionCharIncrement2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range('a','f',2) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"ace\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionCharDecrement2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range('f','a',-2) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"fdb\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionCharIncrement0() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% for i in range('a','e',0) %}{{ i }}{% endfor %}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testRangeFunctionLongVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(0,var) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", 5L);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"012345\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionDoubleVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(0,var) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", 5.5D);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"012345\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionIntegerVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(0,var) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", 5);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"012345\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionIncrementIntegerVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(0,var,increment) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", 5);\n    context.put(\"increment\", 2);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"024\", writer.toString());\n  }\n\n  @Test\n  void testRangeFunctionIncrementDoubleVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in range(0,var,increment) %}{{ i }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"var\", 5);\n    context.put(\"increment\", 2D);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"024\", writer.toString());\n  }\n\n  public class SimpleObject {\n\n    public int small = 1;\n\n    public int large = 20;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/CoreTagsTest.java",
    "content": "/**\n * **************************************************************************** his file is part of\n * Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE file that was distributed\n * with this source code. ****************************************************************************\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass CoreTagsTest {\n\n  public static final String LINE_SEPARATOR = System.lineSeparator();\n\n  @Test\n  void testBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.grandfather.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n  }\n\n  /**\n   * This ensures that block inheritance works properly even if it skips a generation.\n   */\n  @Test\n  void skipGenerationBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.skipGenerationBlock1.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"success\", writer.toString());\n  }\n\n  /**\n   * The template used to fail if the user wrapped the block name in quotes.\n   */\n  @Test\n  void testBlockWithStringLiteralName() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% block 'content' %}hello{% endblock 'content' %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"hello\", writer.toString());\n  }\n\n  @Test\n  void testIf() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if false or steve == true  %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"yes\", true);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  /**\n   * Issue #34\n   */\n  @Test\n  void testIfThenElse() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if alpha %}alpha{% elseif beta %}beta{% else %}gamma{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"alpha\", true);\n    context.put(\"beta\", false);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"alpha\", writer.toString());\n  }\n\n  @Test\n  void testIfWithDirectProperty() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if variable %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"variable\", true);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n\n  @Test\n  void testIfWhenInvalidOrNoEndifTag() throws PebbleException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    String source = \"{% if variable %}smth{ endif %}\";\n    try {\n      pebble.getTemplate(source);\n\n      fail(\"Should fail due to invalid endif tag\");\n    } catch (ParserException ex) {\n      assertEquals(ex.getPebbleMessage(),\n          \"Unexpected end of template. Pebble was looking for the \\\"endif\\\" tag\");\n      assertEquals(ex.getLineNumber(), (Integer) 1);\n      assertEquals(ex.getFileName(), source);\n    }\n  }\n\n  @Test\n  void testFlush() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"start{% flush %}end\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    FlushAwareWriter writer = new FlushAwareWriter();\n    template.evaluate(writer);\n    List<String> flushedBuffers = writer.getFlushedBuffers();\n\n    assertEquals(\"start\", flushedBuffers.get(0));\n    assertEquals(\"startend\", flushedBuffers.get(1));\n  }\n\n  @Test\n  void testFor() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for user in users %}{% if loop.first %}[{{ loop.length }}]{% endif %}{% if loop.last %}[{{ loop.length }}]{% endif %}{{ loop.index }}{{ loop.revindex }}{{ user.username }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    List<User> users = new ArrayList<>();\n    users.add(new User(\"Alex\"));\n    users.add(new User(\"Bob\"));\n    users.add(new User(\"John\"));\n    context.put(\"users\", users);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"[3]02Alex11Bob[3]20John\", writer.toString());\n  }\n\n  @Test\n  void testForWithIterable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for user in users %}{% if loop.first %}[first]{% endif %}{% if loop.last %}[last]{% endif %}{{ loop.index }}{{ user.username }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Iterable<User> users = () -> new Iterator<User>() {\n\n      User[] fixture = new User[]{new User(\"Alex\"), new User(\"Bob\"), new User(\"John\")};\n      int pos = 0;\n\n      @Override\n      public boolean hasNext() {\n        return this.pos < this.fixture.length;\n      }\n\n      @Override\n      public User next() {\n        return this.fixture[this.pos++];\n      }\n\n      @Override\n      public void remove() {\n        throw new UnsupportedOperationException(\n            \"Not supported yet.\"); //To change body of generated methods, choose Tools | Templates.\n      }\n    };\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"users\", users);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"[first]0Alex1Bob[last]2John\", writer.toString());\n  }\n\n\n  @Test\n  void testForWithMap() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    Map<String, Integer> data = new LinkedHashMap<>();\n    data.put(\"One\", 1);\n    data.put(\"Two\", 2);\n    data.put(\"Three\", 3);\n\n    String source = \"{% for entry in data %}{{ entry.key }} {{ entry.value }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"data\", data);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"One 1Two 2Three 3\", writer.toString());\n  }\n\n  @Test\n  void testForSequenceNumber() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for i in 0..5 %}{{i}}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"012345\", writer.toString());\n  }\n\n  @Test\n  void testForSequenceNumberException() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% for i in 'a'..5 %}{{i}}{% endfor %}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testForWhenInvalidOrNoEndforTag() throws PebbleException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    String source = \"{% for i in 'a'..5 %}{{i}}% endfor %}\";\n    try {\n      pebble.getTemplate(source);\n\n      fail(\"Should fail due to invalid endfor tag\");\n    } catch (ParserException ex) {\n      assertEquals(ex.getPebbleMessage(),\n          \"Unexpected end of template. Pebble was looking for the \\\"endfor\\\" tag\");\n      assertEquals(ex.getLineNumber(), (Integer) 1);\n      assertEquals(ex.getFileName(), source);\n    }\n  }\n\n  @Test\n  void testFilterTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% filter upper %}hello{% endfilter %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"HELLO\", writer.toString());\n  }\n\n  @Test\n  void testChainedFilterTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% filter lower | escape %}HELLO<br>{% endfilter %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"hello&lt;br&gt;\", writer.toString());\n  }\n\n  /**\n   * Issue #15\n   */\n  @Test\n  void testForIteratingOverProperty() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for user in classroom.users %}{{ user.username }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    List<User> users = new ArrayList<>();\n    users.add(new User(\"Alex\"));\n    users.add(new User(\"Bob\"));\n    Classroom classroom = new Classroom();\n    classroom.setUsers(users);\n    context.put(\"classroom\", classroom);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"AlexBob\", writer.toString());\n  }\n\n  @Test\n  void testForWithNullIterable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for user in users %}{{ loop.index }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"users\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testForWithArray() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for user in users %}{{ user }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    String[] users = new String[3];\n    users[0] = \"User 1\";\n    users[1] = \"User 2\";\n    users[2] = \"User 3\";\n    context.put(\"users\", users);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"User 1User 2User 3\", writer.toString());\n  }\n\n  @Test\n  void testForWithArrayOfPrimitives() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for num in ints %}{{ num }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    int[] ints = new int[3];\n    ints[0] = 1;\n    ints[1] = 2;\n    ints[2] = 3;\n    context.put(\"ints\", ints);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"123\", writer.toString());\n  }\n\n  /**\n   * There were compilation issues when having two for loops in the same template due to the same\n   * variable name being declared twice.\n   */\n  @Test\n  void multipleForLoops() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"\" + \"{% for user in users %}{{ user.username }}{% endfor %}\"\n        + \"{% for user in users %}{{ user.username }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    List<User> users = new ArrayList<>();\n    users.add(new User(\"Alex\"));\n    users.add(new User(\"Bob\"));\n    context.put(\"users\", users);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"AlexBobAlexBob\", writer.toString());\n  }\n\n  @Test\n  void testForElse() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for user in users %}{{ user.username }}{% else %}yes{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    List<User> users = new ArrayList<>();\n    context.put(\"users\", users);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n\n  @Test\n  void testCache() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% cache 'test' %}{% if foobar %}true{% else %}false{% endif %}{% endcache %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foobar\", true);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n\n    //Value should be cached\n    context.put(\"foobar\", false);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testDisabledCache() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .cacheActive(false).build();\n\n    String source = \"{% cache 'test' %}{% if foobar %}true{% else %}false{% endif %}{% endcache %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foobar\", true);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n\n    //Value should NOT be cached\n    context.put(\"foobar\", false);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testCacheWithVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% cache 'test' + var %}{% if foobar %}true{% else %}false{% endif %}{% endcache %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foobar\", true);\n    context.put(\"var\", 12);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n\n    //Value should be cached\n    context.put(\"foobar\", false);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testCacheWithNoName() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% cache %}{% if foobar %}true{% else %}false{% endif %}{% endcache %}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"foobar\", true);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n      assertEquals(\"true\", writer.toString());\n    });\n  }\n\n  public static class SimpleObjectA {\n\n    private String value;\n\n    public String getValue() {\n      return this.value;\n    }\n\n    void setValue(String value) {\n      this.value = value;\n    }\n  }\n\n  /**\n   * It is important that this object has an identical method signature as SimpleObjectA for the\n   * following tests.\n   *\n   * @author mbosecke\n   */\n  public static class SimpleObjectB {\n\n    private String value;\n\n    public String getValue() {\n      return this.value;\n    }\n\n    void setValue(String value) {\n      this.value = value;\n    }\n  }\n\n  /**\n   * Gets the attribute of an object once so that the attribute is cached within the\n   * GetAttributeExpression then evaluates the exact same template but with a null object.\n   * <p>\n   * Issue #57\n   */\n  @Test\n  void testMemberCacheWithNullObject() throws PebbleException, IOException {\n    SimpleObjectA a = new SimpleObjectA();\n    a.setValue(\"A\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", a);\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ object.value }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"A\", writer.toString());\n\n    context.put(\"object\", null);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  /**\n   * Gets the attribute of an object once so that the attribute is cached within the\n   * GetAttributeExpression then evaluates the exact same template but with a new type of object\n   * that happens to have the same method signature.\n   * <p>\n   * Pull #62\n   */\n  @Test\n  void testMemberCacheWithDifferingObjectTypes() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ object.value }}\");\n\n    SimpleObjectA objectA = new SimpleObjectA();\n    objectA.setValue(\"A\");\n\n    SimpleObjectB objectB = new SimpleObjectB();\n    objectB.setValue(\"B\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", objectA);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"A\", writer.toString());\n\n    // swap out the object with similar one to try and break the cache\n    context.put(\"object\", objectB);\n\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"B\", writer.toString());\n  }\n\n  @Test\n  void testImportWithinBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.importWithinBlock.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\\t<input name=\\\"company\\\" value=\\\"forcorp\\\" type=\\\"text\\\" />\" + LINE_SEPARATOR,\n        writer.toString());\n  }\n\n\n  @Test\n  void testDynamicInclude() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.include.dynamic.peb\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    context.put(\"admin\", false);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"default footer\", writer.toString());\n\n    context.put(\"admin\", true);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"admin footer\", writer.toString());\n  }\n\n  @Test\n  void testNonExistingMacroOrFunction() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n      PebbleTemplate template = pebble.getTemplate(\"{{ nonExisting('test') }}\");\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n    });\n  }\n\n  @Test\n  void testInclude() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.include1.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\n        \"TEMPLATE2\" + LINE_SEPARATOR + \"TEMPLATE1\" + LINE_SEPARATOR + \"TEMPLATE2\" + LINE_SEPARATOR,\n        writer.toString());\n  }\n\n  /**\n   * There was an issue when including a template that had it's own inheritance chain.\n   */\n  @Test\n  void testIncludeInheritance() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.includeInheritance1.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"success\", writer.toString());\n  }\n\n  @Test\n  void testIncludeWithinBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.includeWithinBlock.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"TEMPLATE2\" + LINE_SEPARATOR + \"TEMPLATE1\" + LINE_SEPARATOR, writer.toString());\n  }\n\n  /**\n   * Issue #16\n   */\n  @Test\n  void testIncludePropagatesContext() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.includePropagatesContext.peb\");\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"Mitchell\");\n    template.evaluate(writer, context);\n    assertEquals(\"Mitchell\", writer.toString());\n  }\n\n  /**\n   * Ensures that when including a template it is safe to have conflicting block names.\n   */\n  @Test\n  void testIncludeOverridesBlocks() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.includeOverrideBlock.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"TWO\" + LINE_SEPARATOR + \"ONE\" + LINE_SEPARATOR + \"TWO\" + LINE_SEPARATOR,\n        writer.toString());\n  }\n\n  /**\n   * Ensures that an include with a variable override works even if a null value is passed.\n   */\n  @Test\n  void testIncludeOverridesVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.includeOverrideVariable1.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"One: one (overridden)\" + LINE_SEPARATOR + \"Two: \", writer.toString());\n  }\n\n  @Test\n  void testSet() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set name = 'alex'  %}{{ name }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"steve\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"alex\", writer.toString());\n  }\n\n  @Test\n  void testSetInChildTemplateOutsideOfBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.set.child.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"SUCCESS\", writer.toString());\n  }\n\n  @Test\n  void testReSetInForLoop() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set total = 0 %}{% for i in 1..1 %}{% for item in items %}{% set total = total + item.balance %}{% endfor %}{% endfor %}{{ total }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    List<Map<String, Object>> items = new ArrayList<>();\n    for (int i = 1; i < 4; ++i) {\n      Map<String, Object> item = new HashMap<>();\n      item.put(\"balance\", i);\n      items.add(item);\n    }\n    context.put(\"items\", items);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"6\", writer.toString());\n  }\n\n  @Test\n  void testVerbatim() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% verbatim %}{{ foo }}{{ bar }}{% endverbatim %}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"baz\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"{{ foo }}{{ bar }}\", writer.toString());\n  }\n\n  @Test\n  @Timeout(value = 400, unit = TimeUnit.MILLISECONDS)\n  void testParallel() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .executorService(Executors.newCachedThreadPool()).build();\n    String source = \"beginning {% parallel %}{{ slowObject.first }}{% endparallel %} middle {% parallel %}{{ slowObject.second }}{% endparallel %} end {% parallel %}{{ slowObject.third }}{% endparallel %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"slowObject\", new SlowObject());\n    template.evaluate(writer, context);\n\n    assertEquals(\"beginning first middle second end third\", writer.toString());\n\n  }\n\n\n  /**\n   * The for loop will add variables into the evaluation context during runtime and there was an\n   * issue where the evaluation context wasn't thread safe.\n   */\n  @Test\n  void testParallelTagWhileEvaluationContextIsChanging()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .executorService(Executors.newCachedThreadPool()).build();\n    String source = \"{% for num in array %}{% parallel %}{{ loop.index }}{% endparallel %}{% endfor%}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n\n    context.put(\"array\", new int[10]);\n    template.evaluate(writer, context);\n\n    assertEquals(\"0123456789\", writer.toString());\n  }\n\n  /**\n   * Nested parallel tags were throwing an error.\n   */\n  @Test\n  @Timeout(value = 600, unit = TimeUnit.MILLISECONDS)\n  void testNestedParallel() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .executorService(Executors.newCachedThreadPool()).build();\n    // @formatter:off\n    String source = \"{% parallel %}\"\n        + \"{% parallel %}{{ slowObject.fourth() }}{% endparallel %} {% parallel %}{{ slowObject.first() }}{% endparallel %} \"\n        + \"{% parallel %}{{ slowObject.fourth() }}{% endparallel %} {% parallel %}{{ slowObject.first() }}{% endparallel %}\"\n        + \"{% endparallel %}\";\n    // @formatter:on\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n\n    context.put(\"slowObject\", new SlowObject());\n    template.evaluate(writer, context);\n\n    assertEquals(\"fourth first fourth first\", writer.toString());\n  }\n\n  @Test\n  @Timeout(value = 300, unit = TimeUnit.MILLISECONDS)\n  void testIncludeWithinParallelTag() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true)\n        .executorService(Executors.newCachedThreadPool()).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.parallelInclude1.peb\");\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"slowObject\", new SlowObject());\n    template.evaluate(writer, context);\n    assertEquals(\"first\" + LINE_SEPARATOR + \"TEMPLATE1\" + LINE_SEPARATOR + \"first\",\n        writer.toString());\n  }\n\n  @Test\n  void testParallelWithoutExecutorService() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"beginning {% parallel %}{{ slowObject.first }}{% endparallel %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"slowObject\", new SlowObject());\n    template.evaluate(writer, context);\n\n    assertEquals(\"beginning first\", writer.toString());\n  }\n\n\n  /**\n   * Issue #159\n   */\n  @Test\n  void testParallelWithImport() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .executorService(Executors.newCachedThreadPool()).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.parallelWithImport.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n\n    assertEquals(\"success\", writer.toString());\n  }\n\n  public class SlowObject {\n\n    public String first() {\n      try {\n        Thread.sleep(200);\n      } catch (InterruptedException e) {\n        // TODO Auto-generated catch block\n        e.printStackTrace();\n      }\n      return \"first\";\n    }\n\n    public String second() {\n      try {\n        Thread.sleep(200);\n      } catch (InterruptedException e) {\n        // TODO Auto-generated catch block\n        e.printStackTrace();\n      }\n      return \"second\";\n    }\n\n    public String third() {\n      try {\n        Thread.sleep(200);\n      } catch (InterruptedException e) {\n        // TODO Auto-generated catch block\n        e.printStackTrace();\n      }\n      return \"third\";\n    }\n\n    public String fourth() {\n      try {\n        Thread.sleep(400);\n      } catch (InterruptedException e) {\n        // TODO Auto-generated catch block\n        e.printStackTrace();\n      }\n      return \"fourth\";\n    }\n  }\n\n  public class User {\n\n    private final String username;\n\n    public User(String username) {\n      this.username = username;\n    }\n\n    public String getUsername() {\n      return this.username;\n    }\n  }\n\n  public class Classroom {\n\n    private List<User> users = new ArrayList<>();\n\n    public List<User> getUsers() {\n      return this.users;\n    }\n\n    void setUsers(List<User> users) {\n      this.users = users;\n    }\n  }\n\n  public class FlushAwareWriter extends StringWriter {\n\n    private List<String> buffers = new ArrayList<>();\n\n    @Override\n    public void flush() {\n      this.buffers.add(this.getBuffer().toString());\n      super.flush();\n    }\n\n    public List<String> getFlushedBuffers() {\n      return this.buffers;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/CoreTestsTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass CoreTestsTest {\n\n  @Test\n  void testEven() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 2 is even %}yes{% else %}no{% endif %}{% if 3 is even %}no{% else %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n  /**\n   * Pebble parses numbers as longs so we want to make sure our numerical tests will work even if we\n   * force it to take an int as an input.\n   */\n  @Test\n  void testEvenWithInteger() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if num is even %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"num\", 2);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testNullEven() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% if null is even %}yes{% else %}no{% endif %}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n    });\n  }\n\n  @Test\n  void testOdd() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 2 is odd %}no{% else %}yes{% endif %}{% if 3 is odd %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n  /**\n   * Pebble parses numbers as longs so we want to make sure our numerical tests will work even if we\n   * force it to take an int as an input.\n   */\n  @Test\n  void testOddWithInteger() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if num is odd %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"num\", 3);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testNullOdd() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% if null is odd %}yes{% else %}no{% endif %}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n    });\n  }\n\n  @Test\n  void testNull() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if null is null %}yes{% endif %}{% if obj is null %}yes{% endif %}{% if 2 is null %}no{% else %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyesyes\", writer.toString());\n  }\n\n  @Test\n  void testEmpty() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if null is empty() %}yes{% endif %}{% if '  ' is empty() %}yes{% endif %}{% if obj is empty() %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj\", new ArrayList<String>());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyesyes\", writer.toString());\n  }\n\n  @Test\n  void testIterables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if null is iterable() %}no{% else %}yes{% endif %}{% if obj1 is iterable() %}yes{% else %}no{% endif %}{% if obj2 is iterable() %}no{% else %}yes{% endif %}{% if obj3 is iterable() %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj1\", new ArrayList<String>());\n    context.put(\"obj2\", new HashMap<String, Object>());\n    context.put(\"obj3\", new String[]{});\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyesyesyes\", writer.toString());\n  }\n\n  @Test\n  void testIsnt() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 2 is not odd %}yes{% else %}no{% endif %}{% if null is not iterable() %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n  /**\n   * Using the unary \"not\" operator before a test.\n   *\n   * Issue #27\n   */\n  @Test\n  void testNegativeTest() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if not (2 is odd) %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  /**\n   * Similar to the testNegativeTest() except with an attribute of an object in the context.\n   *\n   * Issue #27\n   */\n  @Test\n  void testNegativeTestOnAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if not (classroom.students is empty) %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"classroom\", new Classroom());\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test\n  void testMapTest() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if {} is map %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n\n  /**\n   * Tests if the test function 'defined' is working.\n   */\n  @Test\n  void testDefined() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if test is defined %}yes{% else %}no{% endif %}{% if test2 is defined %}no{% else %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"test\", \"yes\");\n    template.evaluate(writer, context);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n  /**\n   * Tests if the test function 'defined' is working on maps.\n   */\n  @Test\n  void testDefinedWithMap() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    String source = \"{% if test.test is defined %}yes{% else %}no{% endif %}{% if test.test2 is defined %}no{% else %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n\n    Map<String, Object> map = new HashMap<>();\n    map.put(\"test\", \"yes\");\n    context.put(\"test\", map);\n    template.evaluate(writer, context);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n\n  public static class Classroom {\n\n    public static List<Object> students = new ArrayList<>();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/DynamicNamedArgsTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.escaper.SafeString;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass DynamicNamedArgsTest {\n\n  static class Operation {\n    private String path;\n    private List<String> queryParameters;\n\n    Operation(String path, String... queryParameters) {\n      this.path = path;\n      this.queryParameters = Arrays.asList(queryParameters);\n    }\n\n    public String getPath() {\n      return path;\n    }\n\n    public List<String> getQueryParameters() {\n      return queryParameters;\n    }\n  }\n\n  /**\n   * Query parameters are dynamic.\n   */\n  static class QueryStringArgsNullFilter implements Filter {\n    @Override\n    public Object apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) throws PebbleException {\n      if (!(input instanceof Operation)) {\n        throw new IllegalArgumentException(\"Expected Operation but got \" + input.getClass());\n      }\n      Operation operation = (Operation) input;\n      return new SafeString(operation.getPath() + operation.getQueryParameters().stream()\n          .map(it -> it + \"=\" + args.getOrDefault(it, \"string\"))\n          .collect(Collectors.joining(\"&\", \"?\", \"\")));\n    }\n\n    @Override\n    public List<String> getArgumentNames() {\n      return null;\n    }\n  }\n\n  static class QueryStringArgsEmptyFilter implements Filter {\n    @Override\n    public Object apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) throws PebbleException {\n      if (!(input instanceof Operation)) {\n        throw new IllegalArgumentException(\"Expected Operation but got \" + input.getClass());\n      }\n      Operation operation = (Operation) input;\n      return new SafeString(operation.getPath() + operation.getQueryParameters().stream()\n              .map(it -> it + \"=\" + args.getOrDefault(it, \"string\"))\n              .collect(Collectors.joining(\"&\", \"?\", \"\")));\n    }\n\n    @Override\n    public List<String> getArgumentNames() {\n      return new ArrayList<>();\n    }\n  }\n\n  static class QueryStringExtension extends AbstractExtension {\n    @Override\n    public Map<String, Filter> getFilters() {\n      Map<String, Filter> filters = new HashMap<>();\n      filters.put(\"queryStringArgsNull\", new QueryStringArgsNullFilter());\n      filters.put(\"queryStringArgsEmpty\", new QueryStringArgsEmptyFilter());\n      return filters;\n    }\n  }\n\n  @Test\n  void shouldSupportDynamicNamedArgumentsWhenArgumentsIsNull() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .extension(new QueryStringExtension())\n        .build();\n\n    // Query parameters are dynamic\n    Operation operation = new Operation(\"/library\", \"title\", \"isbn\");\n\n    String source = \"{{operation | queryStringArgsNull(title=\\\"Dune\\\")}}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"operation\", operation);\n    template.evaluate(writer, context);\n    assertEquals(\"/library?title=Dune&isbn=string\", writer.toString());\n  }\n\n  @Test\n  void shouldSupportDynamicNamedArgumentsWhenArgumentsIsEmpty() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .extension(new QueryStringExtension())\n            .build();\n\n    // Query parameters are dynamic\n    Operation operation = new Operation(\"/library\", \"title\", \"isbn\");\n\n    String source = \"{{operation | queryStringArgsEmpty(isbn=\\\"1234\\\")}}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"operation\", operation);\n    template.evaluate(writer, context);\n    assertEquals(\"/library?title=string&isbn=1234\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/EmbedCachingTagTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.pebble.loader.DelegatingLoader;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static java.lang.System.lineSeparator;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n\nclass EmbedCachingTagTest {\n\n  private final PebbleEngine pebble;\n  private final Map<String, Object> context;\n\n  public EmbedCachingTagTest() {\n    StringLoader stringLoader = new StringLoader();\n    ClasspathLoader classpathLoader = new ClasspathLoader();\n    classpathLoader.setPrefix(\"templates/embed/cache\");\n\n    this.pebble = new PebbleEngine.Builder()\n        .loader(new DelegatingLoader(Arrays.asList(\n            classpathLoader,\n            stringLoader\n        )))\n        .strictVariables(false)\n        .cacheActive(true)\n        .build();\n\n    Writer writer = new StringWriter();\n    this.context = new HashMap<>();\n    this.context.put(\"foo\", \"FOO\");\n    this.context.put(\"bar\", \"BAR\");\n  }\n\n  @Test\n  void testEmbedNotChangingCachedTemplate() throws PebbleException, IOException {\n    Writer writer1 = new StringWriter();\n    PebbleTemplate template1 = this.pebble.getTemplate(\"template1.peb\");\n    template1.evaluate(writer1, this.context);\n    String result1 = writer1.toString();\n\n    Writer writer2 = new StringWriter();\n    PebbleTemplate template2 = this.pebble.getTemplate(\"template2.peb\");\n    template2.evaluate(writer2, this.context);\n    String result2 = writer2.toString();\n\n    assertEquals(\"\" +\n            \"BEFORE BASE\" + lineSeparator() +\n            \"EMBED OVERRIDE\" + lineSeparator() +\n            \"AFTER BASE\",\n        result1\n    );\n    assertEquals(\"\" +\n            \"BEFORE BASE\" + lineSeparator() +\n            \"EMBED BASE\" + lineSeparator() +\n                \"AFTER BASE\",\n                result2\n        );\n    }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/EmbedTagTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.pebble.loader.DelegatingLoader;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.utils.Pair;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.nio.file.Files;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nclass EmbedTagTest {\n  private String input;\n  private String templateDirectory;\n\n  private PebbleEngine pebble;\n  private Writer writer;\n  private Map<String, Object> context;\n\n  @ParameterizedTest\n  @ValueSource(strings = {\"test0\"\n      , \"test1\"\n      , \"test2\"\n      , \"test3\"\n      , \"test4\"\n      , \"test5\"\n      , \"test6\"\n      , \"test7\"\n      , \"test8\"\n      , \"test9\"\n      , \"test10\"\n      , \"test11\"\n      , \"test12\"\n      , \"test13\"\n      , \"test14\"\n      , \"test15\"\n      , \"test16\"\n      , \"test17\"\n      , \"test18\"})\n  void tests(String input) throws PebbleException, IOException {\n    this.input = input;\n    this.setUp();\n\n    this.renderTemplateAndCheck();\n  }\n\n  private void setUp() {\n    StringLoader stringLoader = new StringLoader();\n    ClasspathLoader classpathLoader = new ClasspathLoader();\n    this.templateDirectory = \"templates/embed/\" + this.input;\n    classpathLoader.setPrefix(this.templateDirectory);\n\n    this.pebble = new PebbleEngine.Builder()\n        .loader(new DelegatingLoader(Arrays.asList(\n            classpathLoader,\n            stringLoader\n        )))\n        .strictVariables(false)\n        .build();\n\n    this.writer = new StringWriter();\n    this.context = new HashMap<>();\n    this.context.put(\"foo\", \"FOO\");\n    this.context.put(\"bar\", \"BAR\");\n  }\n\n  private void renderTemplateAndCheck() throws PebbleException, IOException {\n    Pair<String, Throwable> actualTemplate = this.renderTemplate();\n    String expectedTemplate = this.getResource(\"./\" + this.templateDirectory + \"/template.result.txt\");\n    String expectedTwigTemplate = this.getResource(\"./\" + this.templateDirectory + \"/template.result.twig.txt\");\n    String expectedError = this.getResource(\"./\" + this.templateDirectory + \"/template.error.txt\");\n\n    // template rendered correctly\n    if (actualTemplate.getLeft() != null) {\n      assertNotNull(actualTemplate.getLeft());\n      assertNull(actualTemplate.getRight());\n\n      assertNotNull(expectedTemplate);\n      assertNull(expectedError);\n\n      assertEquals(expectedTemplate, actualTemplate.getLeft());\n\n      // if Twig could render the same template (meaning it doesn't use Pebble-specific syntax), make sure it renders\n      // the same thing Twig does (ignoring whitespace)\n      if (expectedTwigTemplate != null) {\n        assertNotNull(expectedTwigTemplate);\n        assertEquals(expectedTwigTemplate.replaceAll(\"\\\\s\", \"\"), actualTemplate.getLeft().replaceAll(\"\\\\s\", \"\"));\n      }\n    }\n\n    // template did not render correctly, check the error message\n    else {\n      assertNull(actualTemplate.getLeft());\n      assertNotNull(actualTemplate.getRight());\n\n      actualTemplate.getRight().printStackTrace();\n\n      assertNull(expectedTemplate);\n      assertNotNull(expectedError);\n\n      assertEquals(expectedError, actualTemplate.getRight().getMessage());\n    }\n  }\n\n  private Pair<String, Throwable> renderTemplate() {\n    try {\n      PebbleTemplate template = this.pebble.getTemplate(\"template.peb\");\n      template.evaluate(this.writer, this.context);\n      return new Pair<>(this.writer.toString(), null);\n    } catch (Throwable t) {\n      return new Pair<>(null, t);\n    }\n  }\n\n  private String getResource(String filename) {\n    try {\n      File file = new File(\n          this\n              .getClass()\n              .getClassLoader()\n              .getResource(filename)\n              .getFile()\n      );\n\n      return new String(Files.readAllBytes(file.toPath()));\n    } catch (Exception e) {\n      return null;\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/EnumEqualsTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests if equals is working with enums.\n */\nclass EnumEqualsTest {\n\n  @Test\n  void testEnumComparision() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 'MY_CONSTANT' equals obj2 %}yes{% else %}no{% endif %}{% if obj2 equals 'MY_CONSTANT' %}yes{% else %}no{% endif %}{% if obj2 equals 'OTHER_CONSTANT' %}no{% else %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj2\", TestEnum.MY_CONSTANT);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyesyes\", writer.toString());\n\n  }\n\n  public enum TestEnum {\n\n    MY_CONSTANT,\n\n    OTHER_CONSTANT,\n\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ErrorReportingTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.error.RootAttributeNotFoundException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass ErrorReportingTest {\n\n\n  @Test\n  void testLineNumberErrorReportingWithUnixNewlines() throws PebbleException {\n    ParserException parserException = assertThrows(ParserException.class, () -> {\n      //Arrange\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n      pebble.getTemplate(\"test\\n\\n\\ntest\\ntest\\ntest\\n{% error %}\\ntest\");\n    });\n\n    assertThat(parserException.getMessage()).endsWith(\":7)\");\n  }\n\n  @Test\n  void testLineNumberErrorReportingWithWindowsNewlines() throws PebbleException {\n    ParserException parserException = assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n      pebble.getTemplate(\"test\\r\\n\\r\\ntest\\r\\ntest\\r\\ntest\\r\\n{% error %}\\r\\ntest\");\n    });\n\n    assertThat(parserException.getMessage()).endsWith(\":6)\");\n  }\n\n  @Test\n  void testLineNumberErrorReportingDuringEvaluation() throws PebbleException, IOException {\n    PebbleException pebbleException = assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n      PebbleTemplate template = pebble.getTemplate(\"templates/template.errorReporting.peb\");\n      template.evaluate(new StringWriter());\n    });\n\n    assertThat(pebbleException.getMessage()).endsWith(\":8)\");\n  }\n\n  /**\n   * An error should occur when a Pebble Template Engine instance is configured with\n   * Strict Variables set to true and a template is executed that contains a references\n   * to an undefined property.\n   */\n  @Test\n  void testInvalidPropertyReferenceInStrictMode() throws PebbleException, IOException {\n    RootAttributeNotFoundException rootAttributeNotFoundException = assertThrows(RootAttributeNotFoundException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true)\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ root }}\");\n      template.evaluate(new StringWriter());\n    });\n\n    assertThat(rootAttributeNotFoundException.getAttributeName()).isEqualTo(\"root\");\n    assertThat(rootAttributeNotFoundException.getMessage()).contains(\"Root attribute [root] does not exist or can not be accessed\");\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/EscaperExtensionTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.extension.escaper.EscapeFilter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.math.BigDecimal;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nclass EscaperExtensionTest {\n\n  @Test\n  void testEscapeHtml() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ '&<>\\\"\\\\'' | escape }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"&amp;&lt;&gt;&quot;&#39;\", writer.toString());\n  }\n\n  @Test\n  void testPrintBigDecimal() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    String source = \"{{ num }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    BigDecimal num = new BigDecimal(\"1234E+4\");\n    context.put(\"num\", num);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(num.toPlainString(), writer.toString());\n    assertNotEquals(num.toString(), writer.toString());\n  }\n\n  @Test\n  void testEscapeContextVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | escape(strategy='html') }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"&<>\\\"'\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"&amp;&lt;&gt;&quot;&#39;\", writer.toString());\n  }\n\n  @Test\n  void testEscapeWithNamedArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ '&<>\\\"\\\\'' | escape(strategy='html') }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"&amp;&lt;&gt;&quot;&#39;\", writer.toString());\n  }\n\n  @Test\n  void testAutoescapeLiteral() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ '<br />' }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"<br />\", writer.toString());\n  }\n\n  @Test\n  void testAutoescapePrintExpression() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br />\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"&lt;br /&gt;\", writer.toString());\n  }\n\n  @Test\n  void testAutoescapeNonString() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", Collections.singletonList(\"<br />\"));\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"[&lt;br /&gt;]\", writer.toString());\n  }\n\n  @Test\n  void testDisableAutoEscaping() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .autoEscaping(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br />\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<br />\", writer.toString());\n  }\n\n  @Test\n  void testEscapeIntoAbbreviate() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | escape | abbreviate(5)}}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"1234567\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"12...\", writer.toString());\n  }\n\n  @Test\n  void testDoubleEscaping() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | escape }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br />\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"&lt;br /&gt;\", writer.toString());\n  }\n\n  @Test\n  void testAutoescapeToken() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .autoEscaping(false).build();\n    PebbleTemplate template = pebble.getTemplate(\n        \"{% autoescape 'html' %}{{ text }}{% endautoescape %}\"\n            + \"{% autoescape %}{{ text }}{% endautoescape %}\"\n            + \"{% autoescape false %}{{ text }}{% endautoescape %}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br />\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"&lt;br /&gt;&lt;br /&gt;<br />\", writer.toString());\n  }\n\n  @Test\n  void testAutoEscapingMacroOutput() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ test(danger) }}{% macro test(input) %}<{{ input }}>{% endmacro %}\");\n    Writer writer = new StringWriter();\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"danger\", \"<br>\");\n    template.evaluate(writer, context);\n    assertEquals(\"<&lt;br&gt;>\", writer.toString());\n  }\n\n  @Test\n  void testAutoEscapingInclude() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.autoescapeInclude1.peb\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br>\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<&lt;br&gt;>\", writer.toString());\n  }\n\n  @Test\n  void testAutoEscapingParentFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.autoescapeParent1.peb\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br>\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<&lt;br&gt;>\", writer.toString());\n  }\n\n  @Test\n  void testAutoEscapingBlockFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% block header %}<{{ text }}>{% endblock %}{{ block('header') }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br>\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<&lt;br&gt;><&lt;br&gt;>\", writer.toString());\n  }\n\n  @Test\n  void testCustomEscapingStrategy() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultEscapingStrategy(\"custom\")\n        .addEscapingStrategy(\"custom\", input -> input.replace('a', 'b')).build();\n\n    // replaces all a's with b's\n    PebbleTemplate template = pebble.getTemplate(\"{{ text }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"my name is alex\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"my nbme is blex\", writer.toString());\n  }\n\n  @Test\n  void testEscapeFunction() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .extension(new TestExtension()).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ bad() }}\");\n    Map<String, Object> context = new HashMap<>();\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"&lt;script&gt;alert(&quot;injection&quot;);&lt;/script&gt;\", writer.toString());\n  }\n\n  @Test\n  void testNoEscapeMacro() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ test() }}{% macro test() %}<br/>{% endmacro %}\");\n    Map<String, Object> context = new HashMap<>();\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<br/>\", writer.toString());\n  }\n\n  @Test\n  void testCompareSafeStrings() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text|raw == text|raw }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"a\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testEscapeJson() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .defaultEscapingStrategy(EscapeFilter.JSON_ESCAPE_STRATEGY)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ text }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"{\\\"a\\\": \\\"a/b/c\\\"}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"{\\\\\\\"a\\\\\\\": \\\\\\\"a/b/c\\\\\\\"}\", writer.toString());\n  }\n\n  public static class TestExtension extends AbstractExtension {\n\n    @Override\n    public Map<String, Function> getFunctions() {\n      return Collections.singletonMap(\"bad\", new Function() {\n\n        @Override\n        public List<String> getArgumentNames() {\n          return null;\n        }\n\n        @Override\n        public Object execute(Map<String, Object> args, PebbleTemplate self,\n                              EvaluationContext context, int lineNumber) {\n          return \"<script>alert(\\\"injection\\\");</script>\";\n        }\n\n      });\n    }\n\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ExtendingPebbleTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.attributes.AttributeResolver;\nimport io.pebbletemplates.pebble.attributes.ResolvedAttribute;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass ExtendingPebbleTest {\n\n  /**\n   * Issue #51\n   */\n  @Test\n  void testFilterWithoutArgumentsCanAccessEvaluationContext()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .extension(new CustomExtensionWithFilter())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 'test' | noArgumentsButCanAccessContext }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"success\", writer.toString());\n  }\n\n  @Test\n  void testCustomAttributeResolverEvaluateFirst() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .extension(new CustomExtensionWithAttributeResolver())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ person.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new SimplePerson());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello customAttributeResolver\", writer.toString());\n  }\n\n  private static final class CustomExtensionWithFilter extends AbstractExtension {\n\n    @Override\n    public Map<String, Filter> getFilters() {\n\n      Map<String, Filter> filters = new HashMap<>();\n\n      filters.put(\"noArgumentsButCanAccessContext\", new Filter() {\n\n        @Override\n        public List<String> getArgumentNames() {\n          return null;\n        }\n\n        @Override\n        public String apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                            EvaluationContext context, int lineNumber) {\n          if (context != null && self != null) {\n            return \"success\";\n          } else {\n            return \"failure\";\n          }\n        }\n\n      });\n      return filters;\n    }\n  }\n\n  private static final class CustomExtensionWithAttributeResolver extends AbstractExtension {\n\n    @Override\n    public List<AttributeResolver> getAttributeResolver() {\n\n      List<AttributeResolver> attributeResolvers = new ArrayList<>();\n      attributeResolvers.add(\n          (instance, attribute, argumentValues, args, isStrictVariables, filename, lineNumber) ->\n              new ResolvedAttribute(\"customAttributeResolver\"));\n      return attributeResolvers;\n    }\n  }\n\n  private static class SimplePerson {\n\n    public final String name = \"Bob\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/FileLoaderTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.FileLoader;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.net.URISyntaxException;\nimport java.nio.file.Paths;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\npublic class FileLoaderTest {\n\n  @Test\n  void testFileLoaderPrefixNull() {\n    assertThrows(LoaderException.class, () -> new FileLoader(null));\n  }\n\n  @Test\n  void testFileLoaderPrefixEmpty() {\n    assertThrows(LoaderException.class, () -> new FileLoader(\" \"));\n  }\n\n  @Test\n  void testFileLoaderPrefixRelativePath() {\n    assertThrows(LoaderException.class, () -> new FileLoader(\" ../bar \"));\n  }\n\n  @Test\n  void testFileLoader() throws PebbleException, IOException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    Loader<?> loader = new FileLoader(prefix);\n    loader.setSuffix(\".suffix\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    PebbleTemplate template1 = engine.getTemplate(\"template.loaderTest.peb\");\n    Writer writer1 = new StringWriter();\n    template1.evaluate(writer1);\n    assertEquals(\"SUCCESS\", writer1.toString());\n  }\n\n  @Test\n  void testFileLoaderAbsoluteTemplateName() throws PebbleException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    Loader<?> loader = new FileLoader(prefix);\n    loader.setSuffix(\".suffix\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    assertThrows(LoaderException.class, () -> engine.getTemplate(\"/template.loaderTest.peb\"));\n  }\n\n  @Test\n  void testFileLoaderTemplateNameIsADirectory() throws PebbleException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    Loader<?> loader = new FileLoader(prefix);\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    assertThrows(LoaderException.class, () -> engine.getTemplate(\"loader\"));\n  }\n\n  @Test\n  void testFileLoaderRelativeTemplateName() throws PebbleException, IOException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).getParent().toString();\n    Loader<?> loader = new FileLoader(prefix);\n    loader.setSuffix(\".suffix\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    PebbleTemplate template1 = engine.getTemplate(\"templates/template.loaderTest.peb\");\n    Writer writer1 = new StringWriter();\n    template1.evaluate(writer1);\n    assertEquals(\"SUCCESS\", writer1.toString());\n  }\n\n  @Test\n  void testFileLoaderPathTraversal() throws PebbleException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    Loader<?> loader = new FileLoader(prefix);\n    loader.setSuffix(\".peb\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    assertThrows(LoaderException.class, () -> engine.getTemplate(\"../template-tests/DoubleNestedIfStatement\"));\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = {\"%2e%2e%2f\", \"%2e%2e/\", \"..%2f\", \"%2e%2e%5c\", \"%2e%2e\\\\\", \"..%5c\", \"%252e%252e%255c\", \"..%255c\"})\n  void testFileLoaderPathTraversalEncoded(String relativePath) throws URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    Loader<?> loader = new FileLoader(prefix);\n    loader.setSuffix(\".peb\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    assertThrows(LoaderException.class, () -> engine.getTemplate(relativePath + \"template-tests/DoubleNestedIfStatement\"));\n  }\n\n  @Test\n  void testFileLoaderUnsupportedCharset() throws PebbleException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    Loader<?> loader = new FileLoader(prefix);\n    loader.setCharset(\"foobar\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    assertThrows(LoaderException.class, () -> engine.getTemplate(\"template.loaderTest.peb\"));\n  }\n\n  /**\n   * Tests if relative includes work. Issue #162.\n   */\n  @Test\n  void testFileLoaderPathWithBackslash() throws IOException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new FileLoader(prefix)).build();\n    PebbleTemplate template = pebble.getTemplate(\"relativepath/subdirectory1/template.forwardslashes.peb\".replace(\"/\", \"\\\\\")); // ensure backslashes in all environments\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"included\", writer.toString());\n  }\n\n  /**\n   * Issue #162.\n   */\n  @Test\n  void testFileLoaderPathWithForwardSlash() throws IOException, URISyntaxException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new FileLoader(prefix)).build();\n    PebbleTemplate template = pebble.getTemplate(\"relativepath/subdirectory1/template.backwardslashes.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"included\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ForTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass ForTest {\n  @Test\n  void testForLengthWithOperation() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n\n    String source = \"{% for user in users %}{% if loop.index < ( loop.length - 1) %}{{user.username}}{% endif %}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    List<User> users = new ArrayList<>();\n    users.add(new User(\"Alex\"));\n    users.add(new User(\"Bob\"));\n    users.add(new User(\"John\"));\n    context.put(\"users\", users);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"AlexBob\", writer.toString());\n  }\n\n  @Test\n  void testForRevIndexWithOperation() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n\n    String source = \"{% for user in users %}{% if (loop.revindex - 1) >= 0 %}{{user.username}}{% endif %}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    List<User> users = new ArrayList<>();\n    users.add(new User(\"Alex\"));\n    users.add(new User(\"Bob\"));\n    users.add(new User(\"John\"));\n    context.put(\"users\", users);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"AlexBob\", writer.toString());\n  }\n\n  @Test\n  void testInvalidIdentifierName() {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n\n    try {\n      String source = \"{% for <= in users %}{% endfor %}\";\n      pebble.getTemplate(source);\n      fail(\"Exception not thrown\");\n    } catch (ParserException e) {\n      assertEquals(\"Unexpected token of value \\\"<=\\\" and type OPERATOR, expected token of type NAME ({% for <= in users %}{% endfor %}:1)\", e.getMessage());\n    }\n  }\n\n  public static class User {\n    public final String username;\n\n    public User(String username) {\n      this.username = username;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/GetAttributeTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.attributes.methodaccess.NoOpMethodAccessValidator;\nimport io.pebbletemplates.pebble.error.AttributeNotFoundException;\nimport io.pebbletemplates.pebble.error.ClassAccessException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.error.RootAttributeNotFoundException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.*;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass GetAttributeTest {\n\n  @Test\n  void testOneLayerAttributeNesting() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testAttributeCacheHitting() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}{{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello SteveSteve\", writer.toString());\n  }\n\n  @Test\n  void testMultiLayerAttributeNesting() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"hello {{ object.simpleObject2.simpleObject.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject3());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testHashmapAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    Map<String, String> map = new HashMap<>();\n    map.put(\"name\", \"Steve\");\n    context.put(\"object\", map);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testHashmapAttributeWithArgumentOfNull() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object[missingContextProperty] }}\");\n    Map<String, Object> context = new HashMap<>();\n    Map<String, String> map = new HashMap<>();\n    map.put(\"name\", \"Steve\");\n    context.put(\"object\", map);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello \", writer.toString());\n  }\n\n  @Test\n  void testNonExistingHashMapAttributeWithoutStrictVariables()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ object.nonExisting }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    Map<String, String> map = new HashMap<>();\n    map.put(\"name\", \"Steve\");\n    context.put(\"object\", map);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testNonExistingMapAttributeWithStrictVariables() throws PebbleException, IOException {\n    assertThrows(AttributeNotFoundException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true).build();\n\n      String source = \"{{ object.nonExisting }}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Map<String, Object> context = new HashMap<>();\n      Map<String, String> map = new HashMap<>();\n      map.put(\"name\", \"Steve\");\n      context.put(\"object\", map);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testNonExistingMapAttributeWithStrictVariablesAndEmptyMap() throws PebbleException, IOException {\n    assertThrows(AttributeNotFoundException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true).build();\n\n      String source = \"{{ object.nonExisting }}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new HashMap<>());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testNullMapValueWithoutStrictVariables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ map.name }}\");\n    Map<String, Object> context = new HashMap<>();\n\n    Map<String, Object> map = new HashMap<>();\n    map.put(\"name\", null);\n    context.put(\"map\", map);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello \", writer.toString());\n  }\n\n  /**\n   * Issue 446\n   */\n  @Test\n  void testNullMapValueWithStrictVariables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ map.name }}\");\n    Map<String, Object> context = new HashMap<>();\n\n    Map<String, Object> map = new HashMap<>();\n    map.put(\"name\", null);\n    context.put(\"map\", map);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello \", writer.toString());\n  }\n\n  @Test\n  void testMethodAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject4());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testTwoMethodsAlmostSameName() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.something() }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new ObjectMethodSameName());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  /**\n   * Make sure we are properly accounting for getting the class object from an Object in all\n   * situations:\n   *\n   * | AllowUnsafeMethods | Strict Variables | Access Type | Result  |\n   * | ------------------ | ---------------- | ----------- | ------- |\n   * | true               | false            | property    | allowed |\n   * | true               | false            | method      | allowed |\n   * | true               | true             | property    | allowed |\n   * | true               | true             | method      | allowed |\n   * | false              | false            | property    | throw   |\n   * | false              | false            | method      | throw   |\n   * | false              | true             | property    | throw   |\n   * | false              | true             | method      | throw   |\n   */\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOn_StrictVariableOff_Property()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .methodAccessValidator(new NoOpMethodAccessValidator())\n        .strictVariables(false)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.class }}]\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello [\" + SimpleObject.class.toString() + \"]\", writer.toString());\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOn_StrictVariableOff_Method()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .methodAccessValidator(new NoOpMethodAccessValidator())\n        .strictVariables(false)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.getClass() }}]\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello [\" + SimpleObject.class.toString() + \"]\", writer.toString());\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOn_StrictVariableOn_Property()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .methodAccessValidator(new NoOpMethodAccessValidator())\n        .strictVariables(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.class }}]\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello [\" + SimpleObject.class.toString() + \"]\", writer.toString());\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOn_StrictVariableOn_Method()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .methodAccessValidator(new NoOpMethodAccessValidator())\n        .strictVariables(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.getClass() }}]\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello [\" + SimpleObject.class.toString() + \"]\", writer.toString());\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOff_StrictVariableOff_Property()\n      throws PebbleException, IOException {\n    assertThrows(ClassAccessException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false)\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.class }}]\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new SimpleObject());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOff_StrictVariableOff_Method()\n      throws PebbleException, IOException {\n    assertThrows(ClassAccessException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false)\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.getClass() }}]\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new SimpleObject());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOff_StrictVariableOn_Property()\n      throws PebbleException, IOException {\n    assertThrows(ClassAccessException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true)\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.class }}]\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new SimpleObject());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOff_StrictVariableOn_Method()\n      throws PebbleException, IOException {\n    assertThrows(ClassAccessException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true)\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.getClass() }}]\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new SimpleObject());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOnIsCaseInsensitive_Property()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .methodAccessValidator(new NoOpMethodAccessValidator())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.ClAsS }}]\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello [\" + SimpleObject.class.toString() + \"]\", writer.toString());\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOffIsCaseInsensitive_Property()\n      throws PebbleException, IOException {\n    assertThrows(ClassAccessException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder()\n          .loader(new StringLoader())\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.ClAsS }}]\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new SimpleObject());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOnIsCaseInsensitive_Method()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .methodAccessValidator(new NoOpMethodAccessValidator())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.GeTcLAsS() }}]\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello [\" + SimpleObject.class.toString() + \"]\", writer.toString());\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOffIsCaseInsensitive_Method()\n      throws PebbleException, IOException {\n    assertThrows(ClassAccessException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder()\n          .loader(new StringLoader())\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.GeTcLAsS() }}]\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new SimpleObject());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testAccessingClass_AllowUnsafeMethodsOffForMethodNotify_thenThrowException()\n      throws PebbleException, IOException {\n    assertThrows(ClassAccessException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder()\n          .loader(new StringLoader())\n          .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello [{{ object.notify() }}]\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new SimpleObject());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  /**\n   * The GetAttribute expression involves caching, we test with different objects to make sure that\n   * the caching doesnt have any negative side effects.\n   */\n  @Test\n  void testMethodAttributeWithDifferentObjects() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n\n    Map<String, Object> context1 = new HashMap<>();\n    context1.put(\"object\", new CustomizableObject(\"Alex\"));\n    Writer writer1 = new StringWriter();\n    template.evaluate(writer1, context1);\n    assertEquals(\"hello Alex\", writer1.toString());\n\n    Map<String, Object> context2 = new HashMap<>();\n    context2.put(\"object\", new CustomizableObject(\"Steve\"));\n    Writer writer2 = new StringWriter();\n    template.evaluate(writer2, context2);\n    assertEquals(\"hello Steve\", writer2.toString());\n  }\n\n  @Test\n  void testBeanMethodWithArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name('Steve') }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithLongArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.number(2) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello 2\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithLongArgument2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.number(2L) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello 2\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithTreatLiteralDecimalAsLong() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).literalDecimalTreatedAsInteger(false)\n        .greedyMatchMethod(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.integer(2) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    try {\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n      fail(\"expected PebbleException\");\n    } catch (PebbleException e) {\n      assertEquals(e.getLineNumber(), (Integer) 1);\n      assertEquals(e.getClass(), AttributeNotFoundException.class);\n    }\n  }\n\n  @Test\n  void testBeanMethodWithTreatNumberAsInteger() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).literalDecimalTreatedAsInteger(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.integer(2) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello 2\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithGreedyMatchArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).greedyMatchMethod(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"hello {{ object.integer(2) }} {{ object.short(2) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello 2 2\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithNumberLiteralsAsBigDecimals() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(true).literalNumbersAsBigDecimals(true)\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ 1234567890123456789012345678901234567890 }}\");\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello 1234567890123456789012345678901234567890\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithoutNumberLiteralsAsBigDecimals() throws PebbleException, IOException {\n    assertThrows(NumberFormatException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n              .strictVariables(true).literalNumbersAsBigDecimals(false)\n              .build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello {{ 1234567890123456789012345678901234567890 }}\");\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void testBeanMethodWithOverloadedArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.number(2.0) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello 4.0\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithTwoArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.multiply(2, 3) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello 6\", writer.toString());\n  }\n\n  @Test\n  void testGetMethodAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject5());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testHasMethodAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject9());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testIsMethodAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject6());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve\", writer.toString());\n  }\n\n  @Test\n  void testComplexNestedAttributes() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"hello {{ object.map.SimpleObject2.simpleObject.name }}. My name is {{ object.map.SimpleObject6.name }}.\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new ComplexObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello Steve. My name is Steve.\", writer.toString());\n  }\n\n  @Test\n  void testAttributeOfNullObjectWithStrictVariables() throws PebbleException, IOException {\n    assertThrows(RootAttributeNotFoundException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n    });\n  }\n\n  @Test\n  void testAttributeOfNullObjectWithoutStrictVariables()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", null);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"hello \", writer.toString());\n  }\n\n  @Test\n  void testNonExistingAttributeWithoutStrictVariables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new Object());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello \", writer.toString());\n  }\n\n  @Test\n  void testNonExistingAttributeWithStrictVariables() throws PebbleException, IOException {\n    assertThrows(AttributeNotFoundException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"object\", new Object());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n      assertEquals(\"hello \", writer.toString());\n    });\n  }\n\n  @Test\n  void testNullAttributeWithoutStrictVariables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject7());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello \", writer.toString());\n\n  }\n\n  /**\n   * Should behave the same as it does with strictVariables = false.\n   */\n  @Test\n  void testNullAttributeWithStrictVariables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject7());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello \", writer.toString());\n\n  }\n\n  @Test()\n  void testPrimitiveAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new SimpleObject8());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello true\", writer.toString());\n  }\n\n  @Test\n  void testArrayIndexAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ arr[2] }}\");\n    Map<String, Object> context = new HashMap<>();\n    String[] data = new String[3];\n    data[0] = \"Zero\";\n    data[1] = \"One\";\n    data[2] = \"Two\";\n    context.put(\"arr\", data);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Two\", writer.toString());\n  }\n\n  @Test\n  void testListIndexAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ arr[2] }}\");\n    Map<String, Object> context = new HashMap<>();\n    List<String> data = new ArrayList<>();\n    data.add(\"Zero\");\n    data.add(\"One\");\n    data.add(\"Two\");\n    context.put(\"arr\", data);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Two\", writer.toString());\n  }\n\n  /**\n   * Tests retrieving a non-existing index from a list with strict mode on.\n   */\n  @Test\n  void testListNonExistingIndexAttributeWithStrictMode()\n      throws PebbleException, IOException {\n    assertThrows(AttributeNotFoundException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(true).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ arr[1] }}\");\n      Map<String, Object> context = new HashMap<>();\n      List<String> data = new ArrayList<>();\n      context.put(\"arr\", data);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n      assertEquals(\"Two\", writer.toString());\n    });\n  }\n\n  /**\n   * Tests retrieving a non-existing index from a list with strict mode off.\n   */\n  @Test\n  void testListNonExistingIndexAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ arr[1] }}\");\n    Map<String, Object> context = new HashMap<>();\n    List<String> data = new ArrayList<>();\n    context.put(\"arr\", data);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test()\n  void testInheritedAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new ChildObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello parent\", writer.toString());\n  }\n\n\n  public class Person {\n\n    public final String name = \"Name\";\n    public final String surname = \"Surname\";\n  }\n\n  @Test\n  void testAccessingValueWithSubscriptInLoop() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    String source = \"{% for attribute in ['name', 'surname']%}{{ person[attribute] }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"person\", new Person());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"NameSurname\", writer.toString());\n  }\n\n  public class SimpleObject {\n\n    public final String name = \"Steve\";\n  }\n\n  public class SimpleObject2 {\n\n    public final SimpleObject simpleObject = new SimpleObject();\n  }\n\n  public class SimpleObject3 {\n\n    public final SimpleObject2 simpleObject2 = new SimpleObject2();\n  }\n\n  public class SimpleObject4 {\n\n    public String name() {\n      return \"Steve\";\n    }\n  }\n\n  public class SimpleObject5 {\n\n    public String getName() {\n      return \"Steve\";\n    }\n  }\n\n  public class SimpleObject6 {\n\n    public String isName() {\n      return \"Steve\";\n    }\n  }\n\n  public class SimpleObject7 {\n\n    public String name = null;\n  }\n\n  public class SimpleObject8 {\n\n    public boolean name = true;\n  }\n\n  public class SimpleObject9 {\n\n    public String hasName() {\n      return \"Steve\";\n    }\n  }\n\n  public class ObjectMethodSameName {\n\n    public String something() {\n      return \"Steve\";\n    }\n\n    public boolean hasSomething() {\n      return true;\n    }\n  }\n\n  public class BeanWithMethodsThatHaveArguments {\n\n    public String getName(String name) {\n      return name;\n    }\n\n    public Double getNumber(Double number) {\n      return number * 2;\n    }\n\n    public Long getNumber(Long number) {\n      return number;\n    }\n\n    public Integer getInteger(Integer number) {\n      return number;\n    }\n\n    public Short getShort(short number) {\n      return number;\n    }\n\n    public Long multiply(Long one, Long two) {\n      return one * two;\n    }\n  }\n\n  public class ComplexObject {\n\n    public final Map<String, Object> map = new HashMap<>();\n\n    {\n      this.map.put(\"SimpleObject2\", new SimpleObject2());\n      this.map.put(\"SimpleObject6\", new SimpleObject6());\n    }\n  }\n\n  public class CustomizableObject {\n\n    private final String name;\n\n    public CustomizableObject(String name) {\n      this.name = name;\n    }\n\n    public String getName() {\n      return this.name;\n    }\n  }\n\n  public class ChildObject extends ParentObject {\n\n  }\n\n  public class ParentObject {\n\n    public String getName() {\n      return \"parent\";\n    }\n  }\n\n  @Test()\n  void testPrimitiveArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ obj.getStringFromLong(1) }} {{ obj.getStringFromLongs(1,2) }}\"\n            + \" {{ obj.getStringFromBoolean(true) }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj\", new PrimitiveArguments());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"1 1 2 true\", writer.toString());\n  }\n\n  @Test\n  void testBeanMethodWithNullArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object.name(var) }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new BeanWithMethodsThatHaveArguments());\n    context.put(\"var\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"hello \", writer.toString());\n  }\n\n  public class PrimitiveArguments {\n\n    public String getStringFromLong(long id) {\n      return String.valueOf(id);\n    }\n\n    public String getStringFromLongs(Long first, long second) {\n      return first + \" \" + second;\n    }\n\n    public String getStringFromBoolean(boolean bool) {\n      return String.valueOf(bool);\n    }\n  }\n\n  @Test\n  void testAttributePrimitiveAccessWithEmptyMap() throws Exception {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(String.format(\"hello {{ object[1].name }}\"));\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new HashMap<>());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"hello \", writer.toString());\n  }\n\n  @Test\n  void testAttributePrimitiveAccessWithInteger() throws Exception {\n    String result = this.testAttributePrimitiveAccess(1);\n\n    assertEquals(\"hello Steve\", result);\n  }\n\n  private String testAttributePrimitiveAccess(Number value) throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"hello {{ object[key].name }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"key\", value);\n    context.put(\"object\", Collections.singletonMap(value, new SimpleObject()));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    return writer.toString();\n  }\n\n  @Test\n  void testAttributePrimitiveAccessWithLong() throws Exception {\n    String result = this.testAttributePrimitiveAccess(1L);\n\n    assertEquals(\"hello Steve\", result);\n  }\n\n  @Test\n  void testAttributePrimitiveAccessWithDouble() throws Exception {\n    String result = this.testAttributePrimitiveAccess(1.05D);\n\n    assertEquals(\"hello Steve\", result);\n  }\n\n  @Test\n  void testAttributePrimitiveAccessWithFloat() throws Exception {\n    String result = this.testAttributePrimitiveAccess(1.05F);\n\n    assertEquals(\"hello Steve\", result);\n  }\n\n  @Test\n  void testAttributePrimitiveAccessWithShort() throws Exception {\n    String result = this.testAttributePrimitiveAccess((short) 1);\n\n    assertEquals(\"hello Steve\", result);\n  }\n\n  @Test\n  void testAttributePrimitiveAccessWithByte() throws Exception {\n    String result = this.testAttributePrimitiveAccess((byte) 1);\n\n    assertEquals(\"hello Steve\", result);\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/I18nExtensionTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.Locale;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass I18nExtensionTest {\n\n  @Test\n  void testSimpleLookup() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ i18n('testMessages','greeting') }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"Hello\", writer.toString());\n  }\n\n  @Test\n  void testMessageWithNamedArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ i18n(bundle='testMessages',key='greeting') }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"Hello\", writer.toString());\n  }\n\n  @Test\n  void testLookupWithLocale() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ i18n('testMessages','greeting') }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new Locale(\"es\", \"US\"));\n    assertEquals(\"Hola\", writer.toString());\n  }\n\n  @Test\n  void testLookupSpecialChar() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ i18n('testMessages','greeting.specialchars') }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new Locale(\"es\", \"US\"));\n    assertEquals(\"Hola español\", writer.toString());\n  }\n\n  @Test\n  void testMessageWithParams() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ i18n('testMessages','greeting.someone', 'Pebble') }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new Locale(\"es\", \"US\"));\n    assertEquals(\"Hola, Pebble\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/IncludeWithParameterTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * This class tests if includes with parameters work.\n *\n * @author Thomas Hunziker\n */\nclass IncludeWithParameterTest {\n\n  /**\n   * Test if parameters are processed correctly.\n   */\n  @Test\n  void testIncludeWithParameters() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.includeWithParameter1.peb\");\n    Map<String, Object> context = new HashMap<>();\n\n    context.put(\"contextVariable\", \"some-context-variable\");\n    context.put(\"level\", 1);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    String expectedOutput =\n        \"simple:simple-value\" + \"contextVariable:some-context-variable\" + \"map.position:left\"\n            + \"map.contextVariable:some-context-variable\" + \"level:2\" + \"level-main:1\";\n\n    assertEquals(expectedOutput, writer.toString());\n\n  }\n\n  @Test\n  void testIncludeWithParametersIsolated() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/template.includeWithParameterNotIsolated1.peb\");\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    String expectedOutput = \"bazbar\";\n\n    assertEquals(expectedOutput, writer.toString());\n\n  }\n\n  @Test\n  void testIncludeWithParameterObject() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/template.includeWithParameterObject1.peb\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", new TestObject());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"Hello title\", writer.toString());\n  }\n\n  public static class TestObject {\n\n    public String title = \"title\";\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/InheritanceTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass InheritanceTest {\n\n  private static final String LINE_SEPARATOR = System.lineSeparator();\n\n  @Test\n  void testSimpleInheritance() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.parent.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"GRANDFATHER TEXT ABOVE HEAD\" + LINE_SEPARATOR + LINE_SEPARATOR + \"\\tPARENT HEAD\"\n        + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"GRANDFATHER TEXT BELOW HEAD AND ABOVE FOOT\" + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"\\tGRANDFATHER FOOT\" + LINE_SEPARATOR + LINE_SEPARATOR\n        + \"GRANDFATHER TEXT BELOW FOOT\", writer.toString());\n  }\n\n  @Test\n  void testMultiLevelInheritance() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.child.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"GRANDFATHER TEXT ABOVE HEAD\" + LINE_SEPARATOR + LINE_SEPARATOR + \"\\tCHILD HEAD\"\n        + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"GRANDFATHER TEXT BELOW HEAD AND ABOVE FOOT\" + LINE_SEPARATOR\n        + LINE_SEPARATOR + \"\\tGRANDFATHER FOOT\" + LINE_SEPARATOR + LINE_SEPARATOR\n        + \"GRANDFATHER TEXT BELOW FOOT\", writer.toString());\n  }\n\n  @Test\n  void testDynamicInheritance() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.dynamicChild.peb\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"extendNumberOne\", true);\n\n    Writer writer1 = new StringWriter();\n    template.evaluate(writer1, context);\n    assertEquals(\"ONE\", writer1.toString());\n\n    Writer writer2 = new StringWriter();\n    context.put(\"extendNumberOne\", false);\n    template.evaluate(writer2, context);\n    assertEquals(\"TWO\", writer2.toString());\n  }\n\n  @Test\n  void testNullParent() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% extends null %}success\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"success\", writer.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/LoaderTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.LoaderException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.*;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.*;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nclass LoaderTest {\n\n  @Test\n  void testClassLoaderLoader() throws PebbleException, IOException {\n    Loader<?> loader = new ClasspathLoader();\n    loader.setPrefix(\"templates\");\n    loader.setSuffix(\".peb\");\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    PebbleTemplate template1 = pebble.getTemplate(\"template.loaderTest\");\n    Writer writer1 = new StringWriter();\n    template1.evaluate(writer1);\n    assertEquals(\"SUCCESS\", writer1.toString());\n\n  }\n\n  @Test\n  void testClassLoaderLoaderWithNestedTemplate() throws PebbleException, IOException {\n    Loader<?> loader = new ClasspathLoader();\n    loader.setPrefix(\"templates\");\n    loader.setSuffix(\".peb\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    PebbleTemplate template1 = engine.getTemplate(\"loader/template.loaderTest\");\n    Writer writer1 = new StringWriter();\n    template1.evaluate(writer1);\n    assertEquals(\"SUCCESS\", writer1.toString());\n\n  }\n\n  @Test\n  void testClassLoaderLoaderWithNestedTemplateInJar() throws PebbleException, IOException {\n    URL resource = this.getClass().getResource(\"/templateinjar.jar\");\n    assertNotNull(resource);\n    Loader<?> loader = new ClasspathLoader(new URLClassLoader(new URL[]{resource}, null));\n    loader.setPrefix(\"templates\");\n    loader.setSuffix(\".peb\");\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    PebbleTemplate template1 = engine.getTemplate(\"loader/template.loaderTest\");\n    Writer writer1 = new StringWriter();\n    template1.evaluate(writer1);\n    assertEquals(\"SUCCESS\", writer1.toString());\n\n  }\n\n  @Test\n  void testDelegatingLoader() throws PebbleException, IOException {\n    List<Loader<?>> loaders = new ArrayList<>();\n    loaders.add(new StringLoaderFailure());\n    loaders.add(new StringLoaderOne());\n    loaders.add(new StringLoaderTwo());\n    Loader<?> loader = new DelegatingLoader(loaders);\n\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n    PebbleTemplate template = engine.getTemplate(\"fake template name\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n\n    assertEquals(\"LOADER ONE\", writer.toString());\n  }\n\n  @Test\n  void testDelegatingLoaderWithFileLoaderThatThrowsInvalidPathException() throws URISyntaxException, IOException {\n    String prefix = Paths.get(this.getClass().getClassLoader().getResource(\"templates\").toURI()).toString();\n\n    List<Loader<?>> loaders = new ArrayList<>();\n    loaders.add(new FileLoader(prefix)); // Throws InvalidPathException for HTML content\n    loaders.add(new StringLoader()); // Should handle HTML content as inline template\n\n    DelegatingLoader loader = new DelegatingLoader(loaders);\n    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).build();\n\n    // This simulates passing layout file content (HTML string) to Pebble\n    String layoutContent = \"<main id=\\\"content\\\">Hello {{ name }}</main>\";\n\n    // With the fix, DelegatingLoader catches InvalidPathException and delegates to\n    // StringLoader\n    PebbleTemplate template = engine.getTemplate(layoutContent);\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"World\");\n    template.evaluate(writer, context);\n\n    assertEquals(\"<main id=\\\"content\\\">Hello World</main>\", writer.toString());\n  }\n\n  @Test\n  void testMemoryLoader() throws PebbleException, IOException {\n    MemoryLoader loader = new MemoryLoader();\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();\n\n    loader.addTemplate(\"home.html\", \"{% extends \\\"layout.html\\\" %}{% block title %} Home {% endblock %}\"\n            + \"{% block content %}\"\n            + \"<h1> Home </h1>\"\n            + \"<p> Welcome to my home page. My name is {{ name }}.</p>\"\n            + \"{% endblock %}\");\n    loader.addTemplate(\"layout.html\", \"<html>\"\n            + \"<head>\"\n            + \"<title>Hello Pebble</title>\"\n            + \"</head>\"\n            + \"<body>\"\n            + \"{% block content %}{% endblock %}\"\n            + \"</body>\"\n            + \"</html>\");\n\n    PebbleTemplate template = pebble.getTemplate(\"home.html\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"Bob\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"<html><head><title>Hello Pebble</title></head><body><h1> Home </h1><p> Welcome to my home page. My name is Bob.</p></body></html>\", writer.toString());\n  }\n\n  @Test\n  void testGetLiteralTemplate() throws IOException {\n    PebbleEngine engine = new PebbleEngine.Builder().build();\n    PebbleTemplate template = engine.getLiteralTemplate(\"hello {{ object }}\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"object\", \"world\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"hello world\", writer.toString());\n  }\n\n  /**\n   * Always fail to find a template\n   */\n  private class StringLoaderFailure extends StringLoader {\n\n    @Override\n    public Reader getReader(String templateName) throws LoaderException {\n      throw new LoaderException(null, \"Could not find template \");\n    }\n  }\n\n  private class StringLoaderOne extends StringLoader {\n\n    @Override\n    public Reader getReader(String templateName) throws LoaderException {\n      return new StringReader(\"LOADER ONE\");\n    }\n\n  }\n\n  private class StringLoaderTwo extends StringLoader {\n\n    @Override\n    public Reader getReader(String templateName) throws LoaderException {\n      return new StringReader(\"LOADER TWO\");\n    }\n\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/LogicTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.utils.Pair;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\nclass LogicTest {\n\n  @Test\n  void testUnaryOperators() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if -2 == -+(5 - 3) %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testNotUnaryOperator() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if not (val) %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    Writer writer;\n\n    // \"val\" value not set at all yet\n\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    context.put(\"val\", null);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    context.put(\"val\", false);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    context.put(\"val\", true);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test\n  void testNotUnaryOperatorWithStrictVariables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    String source = \"{% if not (val) %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    Writer writer;\n\n    // \"val\" value not set at all yet\n\n    try {\n      writer = new StringWriter();\n      template.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    try {\n      context.put(\"val\", null);\n      writer = new StringWriter();\n      template.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    context.put(\"val\", false);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    context.put(\"val\", true);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  /**\n   * Issue #36\n   */\n  @Test\n  void testTruthinessOfNullVariableWithoutStrictMode() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if foobar %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    Writer writer = new StringWriter();\n\n    // \"foobar\" value not set at all yet\n\n    template.evaluate(writer, context);\n    assertEquals(\"false\", writer.toString());\n\n    context.put(\"foobar\", null);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"false\", writer.toString());\n\n    context.put(\"foobar\", false);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"false\", writer.toString());\n\n    context.put(\"foobar\", true);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testTruthinessOfNullVariableWithStrictMode() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    String source = \"{% if foobar %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    Writer writer;\n\n    // \"foobar\" value not set at all yet\n\n    try {\n      writer = new StringWriter();\n      template.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    try {\n      writer = new StringWriter();\n      context.put(\"foobar\", null);\n      template.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    writer = new StringWriter();\n    context.put(\"foobar\", false);\n    template.evaluate(writer, context);\n    assertEquals(\"false\", writer.toString());\n\n    writer = new StringWriter();\n    context.put(\"foobar\", true);\n    template.evaluate(writer, context);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testBinaryOperators() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ 8 + 5 * 4 - (6 + 10 / 2)  + 44 }}-{{ 10%3 }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"61-1\", writer.toString());\n  }\n\n  /**\n   * Problem existed where getAttribute would return an Object type which was an invalid operand for\n   * java's algebraic operators.\n   */\n  @Test\n  void testBinaryOperatorOnAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source =\n        \"{{ 1 + item.changeInt }} \" + \"{{ 1 - item.changeInt }} \" + \"{{ 2 * item.changeInt }} \"\n            + \"{{ 11 / item.changeInt }} \" + \"{{ 4 % item.changeInt }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"4 -2 6 3 1\", writer.toString());\n  }\n\n  @Test\n  void testBinaryOperatorsBigDecimal() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ number1 + number2 * number1 / number2 }}-{{number1 % number2}}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", BigDecimal.valueOf(100d));\n    context.put(\"number2\", BigDecimal.valueOf(30d));\n\n    template.evaluate(writer, context);\n    assertEquals(\"200.0-10.0\", writer.toString());\n  }\n\n  @Test\n  void testBinaryOperatorsBigDecimalWithDouble() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ number1 + number2 * number1 / number2 }}-{{number1 % number2}}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", BigDecimal.valueOf(100d));\n    context.put(\"number2\", 30d);\n\n    template.evaluate(writer, context);\n    assertEquals(\"200.0-10.0\", writer.toString());\n  }\n\n  @Test\n  void testBinaryOperatorsBigInteger() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ number1 + number2 * number1 / number2 }}-{{number1 % number2}}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", BigInteger.valueOf(100));\n    context.put(\"number2\", BigInteger.valueOf(30));\n\n    template.evaluate(writer, context);\n    assertEquals(\"200-10\", writer.toString());\n  }\n\n  @Test\n  void testBinaryOperatorsBigIntegerWithLong() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ number1 + number2 * number1 / number2 }}-{{number1 % number2}}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", BigInteger.valueOf(100));\n    context.put(\"number2\", 30L);\n\n    template.evaluate(writer, context);\n    assertEquals(\"200-10\", writer.toString());\n  }\n\n  @Test\n  void testBinaryOperatorsShort() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ number1 + number2 * number1 / number2 }}-{{number1 % number2}}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", (short) 100);\n    context.put(\"number2\", (short) 30);\n\n    template.evaluate(writer, context);\n    assertEquals(\"200-10\", writer.toString());\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"binaryOperatorTestData\")\n  void testBinaryOperatorsWithStringOperands(Object left, Object right, String operator, String expected) throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ left \" + operator + \" right }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"left\", left);\n    context.put(\"right\", right);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(expected, writer.toString());\n  }\n\n  private static Stream<Arguments> binaryOperatorTestData() {\n    return Stream.of(\n        // Addition (+)\n        arguments(10, 2, \"+\", \"12\"),            // int + int\n        arguments(10, \"2\", \"+\", \"102\"),         // int + string = string concatenation\n        arguments(10, 2.5f, \"+\", \"12.5\"),       // int + float\n        arguments(10, 2.5d, \"+\", \"12.5\"),       // int + double\n        arguments(10, 2L, \"+\", \"12\"),           // int + long\n\n        arguments(\"10\", 2, \"+\", \"102\"),         // string + int = string concatenation\n        arguments(\"10\", \"2\", \"+\", \"102\"),       // string + string = string concatenation\n        arguments(\"10\", 2.5f, \"+\", \"102.5\"),    // string + float = string concatenation\n        arguments(\"10\", 2.5d, \"+\", \"102.5\"),    // string + double = string concatenation\n        arguments(\"10\", 2L, \"+\", \"102\"),        // string + long = string concatenation\n\n        arguments(10.5f, 2, \"+\", \"12.5\"),       // float + int\n        arguments(10.5f, \"2\", \"+\", \"10.52\"),    // float + string = string concatenation\n        arguments(10.5f, 2.5f, \"+\", \"13.0\"),    // float + float\n        arguments(10.5f, 2.5d, \"+\", \"13.0\"),    // float + double\n        arguments(10.5f, 2L, \"+\", \"12.5\"),      // float + long\n\n        arguments(10.5d, 2, \"+\", \"12.5\"),       // double + int\n        arguments(10.5d, \"2\", \"+\", \"10.52\"),    // double + string = string concatenation\n        arguments(10.5d, 2.5f, \"+\", \"13.0\"),    // double + float\n        arguments(10.5d, 2.5d, \"+\", \"13.0\"),    // double + double\n        arguments(10.5d, 2L, \"+\", \"12.5\"),      // double + long\n\n        arguments(10L, 2, \"+\", \"12\"),           // long + int\n        arguments(10L, \"2\", \"+\", \"102\"),        // long + string = string concatenation\n        arguments(10L, 2.5f, \"+\", \"12.5\"),      // long + float\n        arguments(10L, 2.5d, \"+\", \"12.5\"),      // long + double\n        arguments(10L, 2L, \"+\", \"12\"),          // long + long\n\n\n        // Subtraction (-)\n        arguments(10, 2, \"-\", \"8\"),             // int - int\n        arguments(10, \"2\", \"-\", \"8\"),           // int - string\n        arguments(10, 2.5f, \"-\", \"7.5\"),        // int - float\n        arguments(10, 2.5d, \"-\", \"7.5\"),        // int - double\n        arguments(10, 2L, \"-\", \"8\"),            // int - long\n\n        arguments(\"10\", 2, \"-\", \"8\"),           // string - int\n        arguments(\"10\", \"2\", \"-\", \"8\"),         // string - string\n        arguments(\"10\", 2.5f, \"-\", \"7.5\"),      // string - float\n        arguments(\"10\", 2.5d, \"-\", \"7.5\"),      // string - double\n        arguments(\"10\", 2L, \"-\", \"8\"),          // string - long\n\n        arguments(10.5f, 2, \"-\", \"8.5\"),        // float - int\n        arguments(10.5f, \"2\", \"-\", \"8.5\"),      // float - string\n        arguments(10.5f, 2.5f, \"-\", \"8.0\"),     // float - float\n        arguments(10.5f, 2.5d, \"-\", \"8.0\"),     // float - double\n        arguments(10.5f, 2L, \"-\", \"8.5\"),       // float - long\n\n        arguments(10.5d, 2, \"-\", \"8.5\"),        // double - int\n        arguments(10.5d, \"2\", \"-\", \"8.5\"),      // double - string\n        arguments(10.5d, 2.5f, \"-\", \"8.0\"),     // double - float\n        arguments(10.5d, 2.5d, \"-\", \"8.0\"),     // double - double\n        arguments(10.5d, 2L, \"-\", \"8.5\"),       // double - long\n\n        arguments(10L, 2, \"-\", \"8\"),            // long - int\n        arguments(10L, \"2\", \"-\", \"8\"),          // long - string\n        arguments(10L, 2.5f, \"-\", \"7.5\"),       // long - float\n        arguments(10L, 2.5d, \"-\", \"7.5\"),       // long - double\n        arguments(10L, 2L, \"-\", \"8\"),           // long - long\n\n        // Multiplication (*)\n        arguments(10, 2, \"*\", \"20\"),            // int * int\n        arguments(10, \"2\", \"*\", \"20\"),          // int * string\n        arguments(10, 2.5f, \"*\", \"25.0\"),       // int * float\n        arguments(10, 2.5d, \"*\", \"25.0\"),       // int * double\n        arguments(10, 2L, \"*\", \"20\"),           // int * long\n\n        arguments(\"10\", 2, \"*\", \"20\"),          // string * int\n        arguments(\"10\", \"2\", \"*\", \"20\"),        // string * string\n        arguments(\"10\", 2.5f, \"*\", \"25.0\"),     // string * float\n        arguments(\"10\", 2.5d, \"*\", \"25.0\"),     // string * double\n        arguments(\"10\", 2L, \"*\", \"20\"),         // string * long\n\n        arguments(10.5f, 2, \"*\", \"21.0\"),       // float * int\n        arguments(10.5f, \"2\", \"*\", \"21.0\"),     // float * string\n        arguments(10.5f, 2.5f, \"*\", \"26.25\"),   // float * float\n        arguments(10.5f, 2.5d, \"*\", \"26.25\"),   // float * double\n        arguments(10.5f, 2L, \"*\", \"21.0\"),      // float * long\n\n        arguments(10.5d, 2, \"*\", \"21.0\"),       // double * int\n        arguments(10.5d, \"2\", \"*\", \"21.0\"),     // double * string\n        arguments(10.5d, 2.5f, \"*\", \"26.25\"),   // double * float\n        arguments(10.5d, 2.5d, \"*\", \"26.25\"),   // double * double\n        arguments(10.5d, 2L, \"*\", \"21.0\"),      // double * long\n\n        arguments(10L, 2, \"*\", \"20\"),           // long * int\n        arguments(10L, \"2\", \"*\", \"20\"),         // long * string\n        arguments(10L, 2.5f, \"*\", \"25.0\"),      // long * float\n        arguments(10L, 2.5d, \"*\", \"25.0\"),      // long * double\n        arguments(10L, 2L, \"*\", \"20\"),          // long * long\n\n\n        // Division (/)\n        arguments(10, 2, \"/\", \"5\"),             // int / int\n        arguments(10, \"2\", \"/\", \"5\"),           // int / string\n        arguments(10, 2.5f, \"/\", \"4.0\"),        // int / float\n        arguments(10, 2.5d, \"/\", \"4.0\"),        // int / double\n        arguments(10, 2L, \"/\", \"5\"),            // int / long\n\n        arguments(\"10\", 2, \"/\", \"5\"),           // string / int\n        arguments(\"10\", \"2\", \"/\", \"5\"),         // string / string\n        arguments(\"10\", 2.5f, \"/\", \"4.0\"),      // string / float\n        arguments(\"10\", 2.5d, \"/\", \"4.0\"),      // string / double\n        arguments(\"10\", 2L, \"/\", \"5\"),          // string / long\n\n        arguments(10.5f, 2, \"/\", \"5.25\"),       // float / int\n        arguments(10.5f, \"2\", \"/\", \"5.25\"),     // float / string\n        arguments(10.5f, 2.5f, \"/\", \"4.2\"),     // float / float\n        arguments(10.5f, 2.5d, \"/\", \"4.2\"),     // float / double\n        arguments(10.5f, 2L, \"/\", \"5.25\"),      // float / long\n\n        arguments(10.5d, 2, \"/\", \"5.25\"),       // double / int\n        arguments(10.5d, \"2\", \"/\", \"5.25\"),     // double / string\n        arguments(10.5d, 2.5f, \"/\", \"4.2\"),     // double / float\n        arguments(10.5d, 2.5d, \"/\", \"4.2\"),     // double / double\n        arguments(10.5d, 2L, \"/\", \"5.25\"),      // double / long\n\n        arguments(10L, 2, \"/\", \"5\"),            // long / int\n        arguments(10L, \"2\", \"/\", \"5\"),          // long / string\n        arguments(10L, 2.5f, \"/\", \"4.0\"),       // long / float\n        arguments(10L, 2.5d, \"/\", \"4.0\"),       // long / double\n        arguments(10L, 2L, \"/\", \"5\"),           // long / long\n\n\n        // Modulo (%)\n        arguments(10, 2, \"%\", \"0\"),             // int % int\n        arguments(10, \"2\", \"%\", \"0\"),           // int % string\n        arguments(10, 2.5f, \"%\", \"0.0\"),        // int % float\n        arguments(10, 2.5d, \"%\", \"0.0\"),        // int % double\n        arguments(10, 2L, \"%\", \"0\"),            // int % long\n\n        arguments(\"10\", 2, \"%\", \"0\"),           // string % int\n        arguments(\"10\", \"2\", \"%\", \"0\"),         // string % string\n        arguments(\"10\", 2.5f, \"%\", \"0.0\"),      // string % float\n        arguments(\"10\", 2.5d, \"%\", \"0.0\"),      // string % double\n        arguments(\"10\", 2L, \"%\", \"0\"),          // string % long\n\n        arguments(10.5f, 2, \"%\", \"0.5\"),        // float % int\n        arguments(10.5f, \"2\", \"%\", \"0.5\"),      // float % string\n        arguments(10.5f, 2.5f, \"%\", \"0.5\"),     // float % float\n        arguments(10.5f, 2.5d, \"%\", \"0.5\"),     // float % double\n        arguments(10.5f, 2L, \"%\", \"0.5\"),       // float % long\n\n        arguments(10.5d, 2, \"%\", \"0.5\"),        // double % int\n        arguments(10.5d, \"2\", \"%\", \"0.5\"),      // double % string\n        arguments(10.5d, 2.5f, \"%\", \"0.5\"),     // double % float\n        arguments(10.5d, 2.5d, \"%\", \"0.5\"),     // double % double\n        arguments(10.5d, 2L, \"%\", \"0.5\"),       // double % long\n\n        arguments(10L, 2, \"%\", \"0\"),            // long % int\n        arguments(10L, \"2\", \"%\", \"0\"),          // long % string\n        arguments(10L, 2.5f, \"%\", \"0.0\"),       // long % float\n        arguments(10L, 2.5d, \"%\", \"0.0\"),       // long % double\n        arguments(10L, 2L, \"%\", \"0\")            // long % long\n    );\n  }\n\n  /**\n   * Problem existed where getAttribute would return an Object type which was an invalid operand for\n   * java's algebraic operators.\n   */\n  @Test\n  void testUnaryOperatorOnAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if -5 > -item.changeInt %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test\n  void testNotUnaryOperatorOnAttribute() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if not(item.truthy) %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test\n  void testLogicOperatorOnAttributes() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if item.truthy and item.falsy %}yes{% else %}no{% endif %}\"\n        + \"{% if item.truthy or item.falsy %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"noyes\", writer.toString());\n  }\n\n  @Test\n  void testLogicOperatorsWithNullValues() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if a %}yes{% else %}no{% endif %}\"\n        + \"{% if b %}yes{% else %}no{% endif %}\"\n        + \"{% if c %}yes{% else %}no{% endif %}\"\n        + \"{% if d %}yes{% else %}no{% endif %}\"\n\n        + \"{% if true and a %}yes{% else %}no{% endif %}\"\n        + \"{% if true and b %}yes{% else %}no{% endif %}\"\n        + \"{% if true and c %}yes{% else %}no{% endif %}\"\n        + \"{% if true and d %}yes{% else %}no{% endif %}\"\n\n        + \"{% if a and true %}yes{% else %}no{% endif %}\"\n        + \"{% if b and true %}yes{% else %}no{% endif %}\"\n        + \"{% if c and true %}yes{% else %}no{% endif %}\"\n        + \"{% if d and true %}yes{% else %}no{% endif %}\"\n\n        + \"{% if false or a %}yes{% else %}no{% endif %}\"\n        + \"{% if false or b %}yes{% else %}no{% endif %}\"\n        + \"{% if false or c %}yes{% else %}no{% endif %}\"\n        + \"{% if false or d %}yes{% else %}no{% endif %}\"\n\n        + \"{% if a or false %}yes{% else %}no{% endif %}\"\n        + \"{% if b or false %}yes{% else %}no{% endif %}\"\n        + \"{% if c or false %}yes{% else %}no{% endif %}\"\n        + \"{% if d or false %}yes{% else %}no{% endif %}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"b\", null);\n    context.put(\"c\", false);\n    context.put(\"d\", true);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"nononoyes\" + \"nononoyes\" + \"nononoyes\" + \"nononoyes\" + \"nononoyes\",\n        writer.toString());\n  }\n\n  @Test\n  void testLogicOperatorsWithNullValuesWithStrictVariables()\n      throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    String andSource = \"{% if a and b %}yes{% else %}no{% endif %}\";\n    String orSource = \"{% if a or b %}yes{% else %}no{% endif %}\";\n    PebbleTemplate andTemplate = pebble.getTemplate(andSource);\n    PebbleTemplate orTemplate = pebble.getTemplate(orSource);\n\n    Map<String, Object> context = new HashMap<>();\n    Writer writer;\n\n    // values not set at all yet\n\n    try {\n      writer = new StringWriter();\n      andTemplate.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    try {\n      writer = new StringWriter();\n      orTemplate.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    context.put(\"a\", null);\n    context.put(\"b\", null);\n\n    try {\n      writer = new StringWriter();\n      andTemplate.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    try {\n      writer = new StringWriter();\n      orTemplate.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    context.put(\"a\", null);\n    context.put(\"b\", false);\n\n    try {\n      writer = new StringWriter();\n      andTemplate.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    try {\n      writer = new StringWriter();\n      orTemplate.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    context.put(\"a\", false);\n    context.put(\"b\", null);\n\n    writer = new StringWriter();\n    andTemplate.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n\n    try {\n      writer = new StringWriter();\n      orTemplate.evaluate(writer, context);\n      fail(\"Exception not thrown\");\n    } catch (PebbleException e) {\n    }\n\n    context.put(\"a\", true);\n    context.put(\"b\", false);\n\n    writer = new StringWriter();\n    andTemplate.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n\n    writer = new StringWriter();\n    orTemplate.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testNotOperatorPrecedence() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if not item.falsy and not item.truthy %}This should not be displayed{% else %}All's good{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"All's good\", writer.toString());\n  }\n\n  @Test\n  void testNotOperatorWithParenthesisPrecedence() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if (not item.falsy) and (not item.truthy) %}This should not be displayed{% else %}All's good{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"All's good\", writer.toString());\n  }\n\n  @Test\n  void testTernary() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ true ? 1 : 2 }}-{{ 1 + 4 == 5 ?(2-1) : 2 }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"1-1\", writer.toString());\n  }\n\n  @Test\n  void testComparisons() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 3 > 2 %}yes{% endif %}\" +\n        \"{% if 2 > 3 %}no{% endif %}\" +\n        \"{% if 2 > 2 %}no{% endif %}\" +\n        \"{% if 2 < 3 %}yes{% endif %}\" +\n        \"{% if 3 < 2 %}no{% endif %}\" +\n        \"{% if 2 < 2 %}no{% endif %}\" +\n        \"{% if 3 >= 3 %}yes{% endif %}\" +\n        \"{% if 3 >= 2 %}yes{% endif %}\" +\n        \"{% if 2 >= 3 %}no{% endif %}\" +\n        \"{% if 3 <= 3 %}yes{% endif %}\" +\n        \"{% if 3 <= 2 %}no{% endif %}\" +\n        \"{% if 2 <= 3 %}yes{% endif %}\" +\n        \"{% if 100 <= 100 %}yes{% endif %}\" +\n        \"{% if 2 == 2 %}yes{% endif %}\" +\n        \"{% if 2 == 3 %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"yesyesyesyesyesyesyesyes\", writer.toString());\n  }\n\n  @Test\n  void testComparisonsOnDifferingOperands() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 3 > 2.0 %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test()\n  void testEqualsOperator() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 'test' equals obj2 %}yes{% endif %}{% if 'blue' equals 'red' %}no{% else %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj2\", \"test\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n  @Test()\n  void testEqualsOperatorWithNulls() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if null equals null %}yes{% endif %}{% if null equals obj %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"obj\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n  @Test()\n  void testNotEqualsOperator() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 'Mitchell' != name %}no{% else %}yes{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name\", \"Mitchell\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test()\n  void testEqualsOperatorWithPrimitives() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 1 equals 1 %}yes{% endif %}{% if 3 equals item.changeInt %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyes\", writer.toString());\n  }\n\n  /**\n   * There was an bug where two Number objects (Integer, Double etc.) were compared for equality\n   * using ==. This was fixed to use equals().\n   *\n   * @see https://github.com/mbosecke/pebble/issues/46\n   */\n  @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n  @Test()\n  void testEqualsOperatorWithNumberObjects() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if (v == 1) %}num1{% elseif (v == 999999) %}num999999{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    // Test Integer, Long, Float and Double.\n    List<Pair<Number, String>> tests = new ArrayList<>();\n    tests.add(new Pair(1, \"num1\"));\n    tests.add(new Pair(999999, \"num999999\"));\n    tests.add(new Pair(1l, \"num1\"));\n    tests.add(new Pair(999999l, \"num999999\"));\n    tests.add(new Pair(1f, \"num1\"));\n    tests.add(new Pair(999999f, \"num999999\"));\n    tests.add(new Pair(1d, \"num1\"));\n    tests.add(new Pair(999999d, \"num999999\"));\n\n    for (Pair<Number, String> test : tests) {\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"v\", test.getLeft());\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n      assertEquals(test.getRight(), writer.toString());\n    }\n  }\n\n  /**\n   * There was an issue where if one of the comparison operands came from a variable object, the\n   * template could not be compiled. This is because the getAttribute() method of the\n   * AbstractPebbleTemplate returns Objects and Objects can not be compared to primitives.\n   */\n  @Test()\n  void testComparisonWithAttributeOperand() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if item.change < 2.0 %}yes{% else %}no{% endif %}\"\n        + \"{% if item.change <= 2.0 %}yes{% else %}no{% endif %}\"\n        + \"{% if item.change > 2.0 %}yes{% else %}no{% endif %}\"\n        + \"{% if item.change >= 2.0 %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"item\", new Item());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyesnono\", writer.toString());\n  }\n\n  @Test()\n  void testComparisonBigDecimal() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if number1 > number2 %}yes{% endif %}\" +\n        \"{% if number2 > number1 %}no{% endif %}\" +\n        \"{% if number2 > number2 %}no{% endif %}\" +\n        \"{% if number2 < number1 %}yes{% endif %}\" +\n        \"{% if number1 < number2 %}no{% endif %}\" +\n        \"{% if number2 < number2 %}no{% endif %}\" +\n        \"{% if number1 >= number1 %}yes{% endif %}\" +\n        \"{% if number1 >= number2 %}yes{% endif %}\" +\n        \"{% if number2 >= number1 %}no{% endif %}\" +\n        \"{% if number1 <= number1 %}yes{% endif %}\" +\n        \"{% if number1 <= number2 %}no{% endif %}\" +\n        \"{% if number2 <= number1 %}yes{% endif %}\" +\n        \"{% if number2 <= number2 %}yes{% endif %}\" +\n        \"{% if number2 == number2 %}yes{% endif %}\" +\n        \"{% if number2 == number1 %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", BigDecimal.valueOf(3d));\n    context.put(\"number2\", BigDecimal.valueOf(2d));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyesyesyesyesyesyesyes\", writer.toString());\n  }\n\n  @Test()\n  void testComparisonWithNull() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if number1 > number2 %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", null);\n    context.put(\"number2\", BigDecimal.valueOf(2d));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test()\n  void testComparisonWithNull2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if number1 > number2 %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", BigDecimal.valueOf(3d));\n    context.put(\"number2\", null);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test()\n  void testComparisonBigDecimalWithDouble() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if number1 > number2 %}yes{% endif %}\" +\n        \"{% if number2 > number1 %}no{% endif %}\" +\n        \"{% if number2 > number2 %}no{% endif %}\" +\n        \"{% if number2 < number1 %}yes{% endif %}\" +\n        \"{% if number1 < number2 %}no{% endif %}\" +\n        \"{% if number2 < number2 %}no{% endif %}\" +\n        \"{% if number1 >= number1 %}yes{% endif %}\" +\n        \"{% if number1 >= number2 %}yes{% endif %}\" +\n        \"{% if number2 >= number1 %}no{% endif %}\" +\n        \"{% if number1 <= number1 %}yes{% endif %}\" +\n        \"{% if number1 <= number2 %}no{% endif %}\" +\n        \"{% if number2 <= number1 %}yes{% endif %}\" +\n        \"{% if number2 <= number2 %}yes{% endif %}\" +\n        \"{% if number2 == number2 %}yes{% endif %}\" +\n        \"{% if number2 == number1 %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"number1\", BigDecimal.valueOf(3d));\n    context.put(\"number2\", 2d);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yesyesyesyesyesyesyesyes\", writer.toString());\n  }\n\n  public class Item {\n\n    public double change = 1.234;\n\n    public Integer changeInt = 3;\n\n    public boolean truthy = true;\n\n    public Boolean falsy = false;\n  }\n\n  @Test()\n  void testIsOperatorPrecedence() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 1 + 2 is odd %} true {% else %} false {% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\" true \", writer.toString());\n  }\n\n  @Test()\n  void testIsOperatorPrecedenceWithAnd() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if 3 is odd and 5 is odd %} true {% else %} false {% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\" true \", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if names contains 'John' %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", Arrays.asList(\"Bob\", \"Maria\", \"John\"));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperatorWithNull() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if null contains 'John' %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testContainsOperator2() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if names contains 'Maria' %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", new HashMap<String, String>() {\n\n      {\n        this.put(\"Bob\", \"Bob\");\n        this.put(\"Maria\", \"Maria\");\n        this.put(\"John\", \"John\");\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator4() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if names contains 'Cobra' %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", Arrays.asList(\"Bob\", \"Maria\", \"John\"));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator5() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if names contains 'Cobra' %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", \"Bob Maria John\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"no\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperatorWithAnd() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if names contains 'Bob' and names contains 'Maria' %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", Arrays.asList(\"Bob\", \"Maria\", \"John\"));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperatorWithOr() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if names contains 'John' or names contains 'Cobra' %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", Arrays.asList(\"Bob\", \"Maria\", \"John\"));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperatorWithNot() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if not (names contains 'Cobra') %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"names\", Arrays.asList(\"Bob\", \"Maria\", \"John\"));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  @Test\n  void testContainsWithArrays() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if values contains value %}yes{% else %}no{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    Writer writer;\n\n    // Objects\n    writer = new StringWriter();\n    context.put(\"values\", new String[]{\"Bob\", \"Marley\"});\n    context.put(\"value\", \"Bob\");\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // boolean\n    writer = new StringWriter();\n    context.put(\"values\", new boolean[]{true, false});\n    context.put(\"value\", Boolean.TRUE);\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // byte\n    writer = new StringWriter();\n    context.put(\"values\", new byte[]{1, 2});\n    context.put(\"value\", (byte) 1);\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // char\n    writer = new StringWriter();\n    context.put(\"values\", new char[]{'a', 'b'});\n    context.put(\"value\", 'a');\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // double\n    writer = new StringWriter();\n    context.put(\"values\", new double[]{1.0d, 2.0d});\n    context.put(\"value\", 1.0d);\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // float\n    writer = new StringWriter();\n    context.put(\"values\", new float[]{1.0f, 2.0f});\n    context.put(\"value\", 1.0f);\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // int\n    writer = new StringWriter();\n    context.put(\"values\", new int[]{1, 2});\n    context.put(\"value\", 1);\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // long\n    writer = new StringWriter();\n    context.put(\"values\", new long[]{1, 2});\n    context.put(\"value\", 1L);\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n\n    // short\n    writer = new StringWriter();\n    context.put(\"values\", new short[]{1, 2});\n    context.put(\"value\", (short) 1);\n    template.evaluate(writer, context);\n    assertEquals(\"yes\", writer.toString());\n  }\n\n  /**\n   * Tests if the string concatenation is working.\n   */\n  @Test\n  void testStringConcatenation() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ name1 ~ name2 ~ name3 | lower }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"name1\", \"Bob\");\n    context.put(\"name2\", \"Maria\");\n    context.put(\"name3\", \"John\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"bobmariajohn\", writer.toString());\n\n  }\n\n  /**\n   * Tests if the macro output SafeString concatenation is working.\n   */\n  @Test\n  void testMacroSafeStringConcatenation() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% macro macro1() %}Bob{% endmacro %}\\n\"\n        + \"{% macro macro2() %}Maria{% endmacro %}\\n\"\n        + \"{% macro macro3() %}John{% endmacro %}\\n\"\n        + \"{{ (macro1() + macro2() + macro3()) | lower }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"bobmariajohn\", writer.toString());\n\n  }\n\n  @Test\n  void testListSizeEmpty() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .build();\n\n    String source = \"{% if dirEntries.size > 0 %}\\n\"\n        + \"<p>There are available files.</p>\\n\"\n        + \"{% else %}\\n\"\n        + \"<p>There are no available files.</p>\\n\"\n        + \"{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"dirEntries\", new ArrayList<>());\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<p>There are no available files.</p>\\n\", writer.toString());\n  }\n\n  @Test\n  void testListSize() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .build();\n\n    String source = \"{% if dirEntries.size > 0 %}\\n\"\n        + \"<p>There are available files.</p>\\n\"\n        + \"{% else %}\\n\"\n        + \"<p>There are no available files.</p>\\n\"\n        + \"{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"dirEntries\", Arrays.asList(\"Test\"));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<p>There are available files.</p>\\n\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/MacroTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.InvocationCountingFunction;\nimport io.pebbletemplates.pebble.extension.TestingExtension;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n/**\n * Created by mitch_000 on 2016-11-13.\n */\nclass MacroTest {\n\n  private static final String LINE_SEPARATOR = System.lineSeparator();\n\n  @Test\n  void testMacro() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.macro1.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\t<input name=\\\"company\\\" value=\\\"google\\\" type=\\\"text\\\" />\" + LINE_SEPARATOR,\n        writer.toString());\n  }\n\n  /**\n   * This ensures that macro inheritance works properly even if it skips a generation.\n   */\n  @Test\n  void skipGenerationMacro() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.skipGenerationMacro1.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"success\", writer.toString());\n  }\n\n  @Test\n  void testMacrosWithSameName() throws PebbleException, IOException {\n    assertThrows(RuntimeException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n      PebbleTemplate template = pebble.getTemplate(\n          \"{{ test() }}{% macro test(one) %}ONE{% endmacro %}{% macro test(one,two) %}TWO{% endmacro %}\");\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      assertEquals(\"\t<input name=\\\"company\\\" value=\\\"google\\\" type=\\\"text\\\" />\" + LINE_SEPARATOR,\n          writer.toString());\n    });\n  }\n\n  @Test\n  void testMacroWithDefaultArgument() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ input(name='country') }}{% macro input(type='text', name) %}{{ type }} {{ name }}{% endmacro %}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"text country\", writer.toString());\n  }\n\n  /**\n   * There was an issue where the second invokation of a macro did not have access to the original\n   * arguments any more.\n   */\n  @Test\n  void testMacroInvokedTwice() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.macroDouble.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"onetwo\", writer.toString());\n  }\n\n  @Test\n  void testFunctionInMacroInvokedTwice() throws PebbleException, IOException {\n\n    TestingExtension extension = new TestingExtension();\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .extension(extension).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\n            \"{{ test() }}{% macro test() %}{{ invocationCountingFunction() }}{% endmacro %}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n\n    InvocationCountingFunction function = extension.getInvocationCountingFunction();\n    assertEquals(1, function.getInvocationCount());\n  }\n\n  @Test\n  void testMacroInvocationWithoutAllArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ test('1') }}{% macro test(one,two) %}{{ one }}{{ two }}{% endmacro %}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"1\", writer.toString());\n  }\n\n  /**\n   * I was once writing macro output directly to writer which was preventing output from being\n   * filtered. I have fixed this now.\n   */\n  @Test\n  void testMacroBeingFiltered() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.macro3.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"HELLO\" + LINE_SEPARATOR, writer.toString());\n  }\n\n  @Test\n  void testImportFile() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.macro2.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\t<input name=\\\"company\\\" value=\\\"forcorp\\\" type=\\\"text\\\" />\" + LINE_SEPARATOR,\n        writer.toString());\n  }\n\n  @Test\n  void testImportInChildTemplateOutsideOfBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.macro.child.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"\t<input name=\\\"company\\\" value=\\\"forcorp\\\" type=\\\"text\\\" />\" + LINE_SEPARATOR,\n        writer.toString());\n  }\n\n  @Test\n  void testDynamicImport() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.import.dynamic.peb\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    context.put(\"modern\", false);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"classic macro\" + LINE_SEPARATOR, writer.toString());\n\n    context.put(\"modern\", true);\n    writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"ajax macro\" + LINE_SEPARATOR, writer.toString());\n  }\n\n  @Test\n  void testSetVariableInsideMacro() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/macros/setVariableBase.peb\");\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"unit\", \"tank\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"tankinfantrytank\", writer.toString());\n  }\n\n  @Test\n  void testMacroHasAccessToGlobalVariables() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    String templateContent = \"{% set foo = 'bar' %}{{ test(_context) }}{% macro test(_context) %}{% set foo = 'foo' %}{{ _context.foo }}{{ foo }}{% endmacro %}\";\n    PebbleTemplate template = pebble.getTemplate(templateContent);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"barfoo\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/MapSyntaxTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.TestingExtension;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass MapSyntaxTest {\n\n  @Test\n  void testMapSyntax() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ {} }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{}\", writer.toString());\n  }\n\n  @Test\n  void testSimpleMap() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ {'key':'value'} }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{key=value}\", writer.toString());\n  }\n\n  @Test\n  void test2ElementMap() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .extension(new TestingExtension())\n        .strictVariables(false).build();\n\n    String source = \"{{ {'key1':'value1','key2':'value2'} | mapToString }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{key1=value1, key2=value2}\", writer.toString());\n  }\n\n  @Test\n  void test2ElementMap2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .extension(new TestingExtension())\n        .strictVariables(false).build();\n\n    String source = \"{{ {'key1' :  'value1'   ,    'key2'      :       'value2' } | mapToString }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{key1=value1, key2=value2}\", writer.toString());\n  }\n\n  @Test\n  void testNElementMap() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .extension(new TestingExtension())\n        .strictVariables(false).build();\n\n    String source = \"{{ {'key1':'value1','key2':'value2','key3':'value3','key4':'value4','key5':'value5'} | mapToString }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{key1=value1, key2=value2, key3=value3, key4=value4, key5=value5}\",\n        writer.toString());\n  }\n\n  @Test\n  void testIncompleteMapSyntax() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ {,} }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testIncompleteMapSyntax2() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ {'key'} }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testIncompleteMapSyntax3() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ {'key':} }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testIncompleteMapSyntax4() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ {:'value'} }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testIncompleteMapSyntax5() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ {'key':'value',} }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @SuppressWarnings(\"serial\")\n  @Test\n  void testMapWithExpressions() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .extension(new TestingExtension())\n        .strictVariables(false).build();\n\n    String source = \"{{ {1:'one', 'two':2, three:'three', numbers['four']:4} | mapToString }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"three\", \"3\");\n    context.put(\"numbers\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"four\", \"4\");\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"{1=one, 3=three, 4=4, two=2}\", writer.toString());\n  }\n\n  @SuppressWarnings({\"serial\", \"unused\"})\n  @Test\n  void testMapWithComplexExpressions() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .extension(new TestingExtension())\n        .strictVariables(false).build();\n\n    String source = \"{{ {'one' + 'plus':'oneplus', 2 - 1:3, three.number:(2+1), 0:numbers['four'][0], numbers ['five'] .value:'five'} | mapToString }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"three\", new Object() {\n\n      public Integer number = 3;\n    });\n    context.put(\"numbers\", new HashMap<String, Object>() {\n\n      {\n        this.put(\"four\", new String[]{\"4\"});\n        this.put(\"five\", new Object() {\n\n          private String value = \"five\";\n\n          public String getValue() {\n            return this.value;\n          }\n        });\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"{0=4, 1=3, 3=3, five=five, oneplus=oneplus}\", writer.toString());\n  }\n\n  @Test\n  void testSetCommand() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set map = {'key'+1:'value'+'1'} %}{{ map }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{key1=value1}\", writer.toString());\n  }\n\n  // this tests use string 'contains' semantics because entry order can't be\n  // trusted\n  @Test\n  void testForTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set names = {'Bob':'Marley','Maria':'Callas','John':'Cobra'} %}{% for name in names %}{{ name.key + '-' + name.value }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    String result = writer.toString();\n    assertTrue(result.indexOf(\"Bob-Marley\") > -1);\n    assertTrue(result.indexOf(\"Maria-Callas\") > -1);\n    assertTrue(result.indexOf(\"John-Cobra\") > -1);\n  }\n\n  @Test\n  void testForTag2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for name in {'Bob':'Marley','Maria':'Callas','John':'Cobra'} %}{{ name.key + '-' + name.value }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    String result = writer.toString();\n    assertTrue(result.indexOf(\"Bob-Marley\") > -1);\n    assertTrue(result.indexOf(\"Maria-Callas\") > -1);\n    assertTrue(result.indexOf(\"John-Cobra\") > -1);\n  }\n\n  @Test\n  void testForElseTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% for name in {} %}{{ name }}{% else %}{{ 'no name' }}{% endfor %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"no name\", writer.toString());\n  }\n\n  @Test\n  void testIfTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if {'Bob':'Marley','Maria':'Callas','John':'Cobra'} is null %}{{ 'it is' }}{% else %}{{ 'it is not' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"it is not\", writer.toString());\n  }\n\n  @Test\n  void testMacroTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% macro print(name) %}{{ name }}{% endmacro %}{{ print({'Bob':'Marley'}) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{Bob=Marley}\", writer.toString());\n  }\n\n  @Test\n  void testMacroTagNamedArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% macro print(name) %}{{ name }}{% endmacro %}{{ print(name={'Bob':'Marley'}) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{Bob=Marley}\", writer.toString());\n  }\n\n  // no operator overloading for maps\n  @Test\n  void testAdditionOverloading() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% set map = {'Bob':'Marley'} + 1 %}{{ map }}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, new HashMap<>());\n    });\n  }\n\n  @Test\n  void testSubtractionOverloading() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{% set map = {'Bob':'Marley'} - 1 %}{{ map }}\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, new HashMap<>());\n    });\n  }\n\n  @Test\n  void testEmptyTest() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if {'John':'Cobra'} is empty %}{{ 'true' }}{% else %}{{ 'false' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testEmptyTest2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if {} is empty %}{{ 'true' }}{% else %}{{ 'false' }}{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testIterableTest() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if {} is iterable %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if {'Bob':'Marley','Maria':'Callas','John':'Cobra'} contains 'Maria' %}true{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if not ( {'Bob':'Marley','Maria':'Callas','John':'Cobra'} contains 'Freddie') %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testContainsOperator3() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% if {'Bob':'Marley','Maria':'Callas','John':'Cobra'} contains 'John' and not ({'Freddie':'Mercury'} contains 'Bob') %}true{% else %}false{% endif %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testNestedMaps() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ { 1 : {}, 2 : { 1 : 1 }, { 3 : 3} : 3, 4 : { 4 : { 4 : 4 } } } }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{{3=3}=3, 1={}, 2={1=1}, 4={4={4=4}}}\", writer.toString());\n  }\n\n  @Test\n  void testNestedArrayInMap() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ {'array':[]} }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"{array=[]}\", writer.toString());\n  }\n\n  // brace syntax regression tests\n\n  @Test\n  void testBraceSyntax() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{% set var = true %}{{ 'hi' }}{# comment #}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"hi\", writer.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/MaxRenderedSizeTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.StringWriter;\nimport java.io.Writer;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass MaxRenderedSizeTest {\n    @Test\n    void renderingExplodingMacroWithLimitWillThrowPebbleException() {\n        PebbleEngine pebble = new PebbleEngine.Builder()\n                .maxRenderedSize(1000)\n                .build();\n\n        PebbleTemplate template = pebble.getTemplate(\"templates/template.macro.exploding.peb\");\n\n        Writer writer = new StringWriter();\n\n        PebbleException thrown = assertThrows(PebbleException.class, () -> template.evaluate(writer));\n        String result = writer.toString();\n        // We didn't write more than allowed.\n        assertTrue(result.length() <= 1000);\n        assertTrue(thrown.getMessage().contains(\"1000\"));\n    }\n\n    @Test\n    @Disabled(\"This test passes but takes about a minute to do so. Creating a faster macro bomb would be nice.\")\n    void renderingExplodingMacroWithoutLimitWillThrowOOMException() {\n        PebbleEngine pebble = new PebbleEngine.Builder()\n                .build();\n\n        PebbleTemplate template = pebble.getTemplate(\"templates/template.macro.exploding.peb\");\n\n        StringWriter writer = new StringWriter();\n\n        assertThrows(OutOfMemoryError.class, () -> template.evaluate(writer));\n    }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/MethodAccessTemplateTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport io.pebbletemplates.pebble.error.ClassAccessException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.attributes.methodaccess.NoOpMethodAccessValidator;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.function.Executable;\n\nclass MethodAccessTemplateTest {\n\n  @Nested\n  class ClassTest {\n\n    @Test\n    void testIfAccessIsForbiddenWhenAllowUnsafeMethodsIsFalse() {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.pebbleEngine();\n      assertThrows(ClassAccessException.class, this.templateEvaluation(pebble));\n    }\n\n    @Test\n    void testIfAccessIsAllowedWhenAllowUnsafeMethodsIsTrue() throws Throwable {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.unsafePebbleEngine();\n      this.templateEvaluation(pebble).execute();\n    }\n\n    private Executable templateEvaluation(PebbleEngine pebble) {\n      return () -> {\n        String source = \"{{clazz.getPackage()}}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"clazz\", Object.class);\n        MethodAccessTemplateTest.this.evaluateTemplate(template, context);\n      };\n    }\n  }\n\n  @Nested\n  class RuntimeTest {\n\n    @Test\n    void testIfAccessIsForbiddenWhenAllowUnsafeMethodsIsFalse() {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.pebbleEngine();\n      assertThrows(ClassAccessException.class, this.templateEvaluation(pebble));\n    }\n\n    @Test\n    void testIfAccessIsAllowedWhenAllowUnsafeMethodsIsTrue() throws Throwable {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.unsafePebbleEngine();\n      this.templateEvaluation(pebble).execute();\n    }\n\n    private Executable templateEvaluation(PebbleEngine pebble) {\n      return () -> {\n        String source = \"{{runtime.availableProcessors()}}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"runtime\", Runtime.getRuntime());\n        MethodAccessTemplateTest.this.evaluateTemplate(template, context);\n      };\n    }\n  }\n\n  @Nested\n  class ThreadTest {\n\n    @Test\n    void testIfAccessIsForbiddenWhenAllowUnsafeMethodsIsFalse() {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.pebbleEngine();\n      assertThrows(ClassAccessException.class, this.templateEvaluation(pebble));\n    }\n\n    @Test\n    void testIfAccessIsAllowedWhenAllowUnsafeMethodsIsTrue() throws Throwable {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.unsafePebbleEngine();\n      this.templateEvaluation(pebble).execute();\n    }\n\n    private Executable templateEvaluation(PebbleEngine pebble) {\n      return () -> {\n        String source = \"{{thread.sleep(500)}}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"thread\", new Thread());\n        MethodAccessTemplateTest.this.evaluateTemplate(template, context);\n      };\n    }\n  }\n\n  @Nested\n  class MethodTest {\n\n    @Test\n    void testIfAccessIsForbiddenWhenAllowUnsafeMethodsIsFalse() {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.pebbleEngine();\n      assertThrows(ClassAccessException.class, this.templateEvaluation(pebble));\n    }\n\n    @Test\n    void testIfAccessIsAllowedWhenAllowUnsafeMethodsIsTrue() throws Throwable {\n      PebbleEngine pebble = MethodAccessTemplateTest.this.unsafePebbleEngine();\n      this.templateEvaluation(pebble).execute();\n    }\n\n    private Executable templateEvaluation(PebbleEngine pebble) {\n      return () -> {\n        Class<?> systemClass = Class.forName(\"java.lang.System\");\n        Method gcMethod = systemClass.getMethod(\"gc\");\n        gcMethod.setAccessible(true);\n        gcMethod.invoke(null);\n\n        String source = \"{{gc.invoke(null, null)}}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"gc\", gcMethod);\n\n        MethodAccessTemplateTest.this.evaluateTemplate(template, context);\n      };\n    }\n  }\n\n  private PebbleEngine unsafePebbleEngine() {\n    return new PebbleEngine.Builder().loader(new StringLoader())\n        .methodAccessValidator(new NoOpMethodAccessValidator())\n        .build();\n  }\n\n  private PebbleEngine pebbleEngine() {\n    return new PebbleEngine.Builder().loader(new StringLoader()).build();\n  }\n\n  private void evaluateTemplate(PebbleTemplate template, Map<String, Object> context)\n      throws IOException {\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n  }\n}"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/NewlineTrimmingTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass NewlineTrimmingTest {\n\n  @Test\n  void testPrintDefault() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{param1}}\\n{{param2}}\");\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"param1\", \"val1\");\n    params.put(\"param2\", \"val2\");\n\n    template.evaluate(writer, params);\n\n    assertEquals(\"val1val2\", writer.toString());\n  }\n\n  @Test\n  void testPrintForceToTrue() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .newLineTrimming(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{param1}}\\n{{param2}}\");\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"param1\", \"val1\");\n    params.put(\"param2\", \"val2\");\n\n    template.evaluate(writer, params);\n\n    assertEquals(\"val1val2\", writer.toString());\n  }\n\n  /**\n   * Given that Newline Trimming is disabled,\n   * a template that contains one newline character with text on each line\n   * should output one newline character.\n   */\n  @Test\n  void testNewLineIncludedWhen_NewLineTrimmingIsFalse() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .newLineTrimming(false)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{param1}}\\n{{param2}}\");\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"param1\", \"val1\");\n    params.put(\"param2\", \"val2\");\n\n    template.evaluate(writer, params);\n\n    assertEquals(\"val1\\nval2\", writer.toString());\n  }\n\n  @Test\n  void testPrintDefaultTwoNewlines() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{param1}}\\n\\n{{param2}}\");\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"param1\", \"val1\");\n    params.put(\"param2\", \"val2\");\n\n    template.evaluate(writer, params);\n\n    assertEquals(\"val1\\nval2\", writer.toString());\n  }\n\n  /**\n   * Given that Newline Trimming is disabled,\n   * a template that contains one or more consecutive newline characters\n   * should output one newline character.\n   */\n  @Test\n  void testOneNewLineWhen_NewLineTrimmingFalseAndConsecutiveNewLinesInTemplate() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .newLineTrimming(false)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{param1}}\\n\\n{{param2}}\");\n\n    Writer writer = new StringWriter();\n\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"param1\", \"val1\");\n    params.put(\"param2\", \"val2\");\n\n    template.evaluate(writer, params);\n\n    assertEquals(\"val1\\n\\nval2\", writer.toString());\n  }\n\n  @Test\n  void testCommentDefault() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{# comment1 #}\\n{# comment2 #}\");\n\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testCommentForceToTrue() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .newLineTrimming(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{# comment1 #}\\n{# comment2 #}\");\n\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testCommentSetToFalse() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .newLineTrimming(false)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{# comment1 #}\\n{# comment2 #}\");\n\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n\n    assertEquals(\"\\n\", writer.toString());\n  }\n\n  @Test\n  void testExecuteDefault() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% if true %}\\n{% endif %}\");\n\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testExecuteForceToTrue() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .newLineTrimming(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% if true %}\\n{% endif %}\");\n\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void testExecuteSetToFalse() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .newLineTrimming(false)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% if true %}\\n{% endif %}\");\n\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n\n    assertEquals(\"\\n\", writer.toString());\n  }\n\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/Nl2brFilterTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.core.Nl2brFilter;\nimport io.pebbletemplates.pebble.extension.escaper.SafeString;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n/**\n * Tests for nl2br filter\n */\nclass Nl2brFilterTest {\n\n    private final Nl2brFilter filter = new Nl2brFilter();\n\n    private static String asString(Object o) {\n        if (o == null) return null;\n        if (o instanceof SafeString) return o.toString();\n        return String.valueOf(o);\n    }\n\n    @Test\n    void testEmptyString() throws PebbleException {\n        Object out = filter.apply(\"\", null, null, null, 0);\n        assertEquals(\"\", asString(out));\n    }\n\n    @Test\n    void testNonStringInputThrows() {\n        assertThrows(IllegalArgumentException.class, () -> filter.apply(42, null, null, null, 0));\n    }\n\n    @Test\n    void testLfOnly() throws PebbleException {\n        Object out = filter.apply(\"A\\nB\\nC\", null, null, null, 0);\n        assertEquals(\"A<br />B<br />C\", asString(out));\n    }\n\n    @Test\n    void testCrOnly() throws PebbleException {\n        Object out = filter.apply(\"A\\rB\\rC\", null, null, null, 0);\n        assertEquals(\"A<br />B<br />C\", asString(out));\n    }\n\n    @Test\n    void testCrLfOnly() throws PebbleException {\n        Object out = filter.apply(\"A\\r\\nB\\r\\nC\", null, null, null, 0);\n        assertEquals(\"A<br />B<br />C\", asString(out));\n    }\n\n    @Test\n    void testMixedNewlines() throws PebbleException {\n        Object out = filter.apply(\"A\\nB\\rC\\r\\nD\", null, null, null, 0);\n        assertEquals(\"A<br />B<br />C<br />D\", asString(out));\n    }\n\n    @Test\n    void testNoNewlineReturnsSafeString() throws PebbleException {\n        String input = \"NoNewlinesHere\";\n        Object out = filter.apply(input, null, null, null, 0);\n        assertEquals(input, asString(out));\n    }\n\n    @Test\n    void testNullInput() throws PebbleException {\n        Object out = filter.apply(null, null, null, null, 0);\n        assertNull(out);\n    }\n\n    @Test\n    void testIntegrationWithEngine() throws IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n        PebbleTemplate template = pebble.getTemplate(\"{{ txt | nl2br | raw }}\");\n        Writer w = new StringWriter();\n        template.evaluate(w, java.util.Collections.singletonMap(\"txt\", \"Line1\\nLine2\\rLine3\\r\\nLine4\"));\n        assertEquals(\"Line1<br />Line2<br />Line3<br />Line4\", w.toString());\n    }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/OverloadedMethodTest.java",
    "content": "/*******************************************************************************\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n ******************************************************************************/\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n// These tests verify correct behavior when resolving overloaded methods in a model class. See issue #367\nclass OverloadedMethodTest {\n\n// Verify that an overloaded method will select the correct version of the method to call based on the input type\n//----------------------------------------------------------------------------------------------------------------------\n\n    public static class Model {\n\n        public String testMethod(String input) {\n            return \"string input: \" + input;\n        }\n\n        public String testMethod(Integer input) {\n            return \"Integer input: \" + input;\n        }\n\n        public String testMethod(Long input) {\n            return \"Long input: \" + input;\n        }\n\n        public String testMethod(Object input) {\n            return \"other input: \" + input.getClass();\n        }\n\n        public String testMethod2(String input1, String input2) {\n            return \"string-string inputs\";\n        }\n\n        public String testMethod2(Object input1, String input2) {\n            return \"object-string inputs\";\n        }\n\n        public String testMethod2(String input1, Object input2) {\n            return \"string-object inputs\";\n        }\n\n        public String testMethod2(Object input1, Object input2) {\n            return \"object-object inputs\";\n        }\n\n    }\n\n    @Test\n    void testWithLiteralString() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .strictVariables(false)\n            .build();\n\n        String input = \"{{ model.testMethod(\\\"one\\\") }}\";\n        String expected = \"string input: one\";\n\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"model\", new Model());\n\n        PebbleTemplate template = pebble.getTemplate(input);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, context);\n        assertEquals(expected, writer.toString());\n    }\n\n    @Test\n    void testWithContextString() throws PebbleException, IOException {\n        this.testWithModel(new Model(), \"one\", \"string input: one\");\n    }\n\n    @Test\n    void testWithInteger() throws PebbleException, IOException {\n        this.testWithModel(new Model(), 1, \"Integer input: 1\");\n    }\n\n    @Test\n    void testWithLong() throws PebbleException, IOException {\n        this.testWithModel(new Model(), 1L, \"Long input: 1\");\n    }\n\n    @Test\n    void testWithObject() throws PebbleException, IOException {\n        this.testWithModel(new Model(), this, \"other input: \" + this.getClass().toString());\n    }\n\n    @Test\n    void testWithStringString() throws PebbleException, IOException {\n        this.testModelWith2Inputs(\"\", \"\", \"string-string inputs\");\n    }\n\n    @Test\n    void testWithStringObject() throws PebbleException, IOException {\n        this.testModelWith2Inputs(\"\", this, \"string-object inputs\");\n    }\n\n    @Test\n    void testWithObjectString() throws PebbleException, IOException {\n        this.testModelWith2Inputs(this, \"\", \"object-string inputs\");\n    }\n\n    @Test\n    void testWithObjectObject() throws PebbleException, IOException {\n        this.testModelWith2Inputs(this, this, \"object-object inputs\");\n    }\n\n// Verify that multiple overloaded methods can be called in the same template, and the member cache will not return the\n// wrong method and cause a ClassCastException.\n//----------------------------------------------------------------------------------------------------------------------\n\n    public static class Model2 {\n\n        public String testMethod(BaseClass input) {\n            return \"BaseClass input\";\n        }\n\n        public String testMethod(ChildClass1 input) {\n            return \"ChildClass1 input\";\n        }\n\n        public String testMethod(ChildClass2 input) {\n            return \"ChildClass2 input\";\n        }\n\n    }\n\n    public static class BaseClass {\n\n    }\n\n    public static class ChildClass1 {\n\n    }\n\n    public static class ChildClass2 {\n\n    }\n\n    @Test\n    void testWithMultipleCalls() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .strictVariables(false)\n            .build();\n\n        String input = \"{{ model.testMethod(BaseClass) }}, {{ model.testMethod(ChildClass1) }}, {{ model.testMethod(ChildClass2) }}\";\n        String expected = \"BaseClass input, ChildClass1 input, ChildClass2 input\";\n\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"model\", new Model2());\n        context.put(\"BaseClass\", new BaseClass());\n        context.put(\"ChildClass1\", new ChildClass1());\n        context.put(\"ChildClass2\", new ChildClass2());\n\n        PebbleTemplate template = pebble.getTemplate(input);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, context);\n        assertEquals(expected, writer.toString());\n    }\n\n// Check that method resolution works with a deep class hierarchy\n//----------------------------------------------------------------------------------------------------------------------\n\n    public static class Model3 {\n\n        public String testMethod(BaseClass input) {\n            return \"BaseClass input\";\n        }\n\n        public String testMethod(ChildClass1 input) {\n            return \"ChildClass1 input\";\n        }\n\n        public String testMethod(ChildClass2 input) {\n            return \"ChildClass2 input\";\n        }\n\n        public String testMethod(ChildClass3 input) {\n            return \"ChildClass3 input\";\n        }\n\n        public String testMethod(ChildClass4 input) {\n            return \"ChildClass4 input\";\n        }\n\n        public String testMethod(ChildClass5 input) {\n            return \"ChildClass5 input\";\n        }\n\n    }\n\n    public static class ChildClass3 extends ChildClass2 {\n\n    }\n\n    public static class ChildClass4 extends ChildClass3 {\n\n    }\n\n    public static class ChildClass5 extends ChildClass4 {\n\n    }\n\n    @Test\n    void testWithBaseClass() throws PebbleException, IOException {\n        this.testWithModel(new Model3(), new BaseClass(), \"BaseClass input\");\n    }\n\n    @Test\n    void testWithChildClass1() throws PebbleException, IOException {\n        this.testWithModel(new Model3(), new ChildClass1(), \"ChildClass1 input\");\n    }\n\n    @Test\n    void testWithChildClass2() throws PebbleException, IOException {\n        this.testWithModel(new Model3(), new ChildClass2(), \"ChildClass2 input\");\n    }\n\n    @Test\n    void testWithChildClass3() throws PebbleException, IOException {\n        this.testWithModel(new Model3(), new ChildClass3(), \"ChildClass3 input\");\n    }\n\n    @Test\n    void testWithChildClass4() throws PebbleException, IOException {\n        this.testWithModel(new Model3(), new ChildClass4(), \"ChildClass4 input\");\n    }\n\n    @Test\n    void testWithChildClass5() throws PebbleException, IOException {\n        this.testWithModel(new Model3(), new ChildClass5(), \"ChildClass5 input\");\n    }\n\n// test helpers\n//----------------------------------------------------------------------------------------------------------------------\n\n    private void testWithModel(Object model, Object modelInput, String expected) throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder()\n                .loader(new StringLoader())\n                .strictVariables(false)\n                .build();\n\n        String input = \"{{ model.testMethod(input) }}\";\n\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"model\", model);\n        context.put(\"input\", modelInput);\n\n        PebbleTemplate template = pebble.getTemplate(input);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, context);\n        assertEquals(expected, writer.toString());\n    }\n\n    private void testModelWith2Inputs(Object modelInput1, Object modelInput2, String expected) throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder()\n                .loader(new StringLoader())\n                .strictVariables(false)\n                .build();\n\n        String input = \"{{ model.testMethod2(input1, input2) }}\";\n\n        Map<String, Object> context = new HashMap<>();\n        context.put(\"model\", new Model());\n        context.put(\"input1\", modelInput1);\n        context.put(\"input2\", modelInput2);\n\n        PebbleTemplate template = pebble.getTemplate(input);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, context);\n        assertEquals(expected, writer.toString());\n    }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/OverrideCoreExtensionTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.node.expression.UnaryExpression;\nimport io.pebbletemplates.pebble.node.expression.BinaryExpression;\nimport io.pebbletemplates.pebble.operator.Associativity;\nimport io.pebbletemplates.pebble.operator.BinaryOperator;\nimport io.pebbletemplates.pebble.operator.BinaryOperatorImpl;\nimport io.pebbletemplates.pebble.operator.UnaryOperator;\nimport io.pebbletemplates.pebble.operator.UnaryOperatorImpl;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass OverrideCoreExtensionTest {\n\n  @Test\n  void testOverrideCodeExtensionFunction() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .extension(new TestExtension())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{i18n()}}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"custom i18n function\", writer.toString());\n  }\n\n  @Test\n  void testOverrideCodeExtensionFilter() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .extension(new TestExtension())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ null | date }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"custom date filter\", writer.toString());\n  }\n\n  @Test\n  void testOverrideCoreExtensionUnaryOperator() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .extension(new TestExtension())\n        .allowOverrideCoreOperators(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ not true }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"custom unary operator\", writer.toString());\n  }\n\n  @Test\n  void testByDefaultPreventsOverrideCoreExtensionUnaryOperator() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .extension(new TestExtension())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ not true }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testOverrideCoreExtensionBinaryOperator() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .extension(new TestExtension())\n        .allowOverrideCoreOperators(true)\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 2 == 2 }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"custom binary operator\", writer.toString());\n  }\n\n  @Test\n  void testByDefaultPreventsOverrideCoreExtensionBinaryOperator() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .extension(new TestExtension())\n        .build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 2 == 2 }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  private static class TestExtension extends AbstractExtension {\n\n    @Override\n    public Map<String, Function> getFunctions() {\n      Map<String, Function> functions = new HashMap<>();\n      functions.put(\"i18n\", new CustomI18nFunction());\n      return functions;\n    }\n\n    @Override\n    public Map<String, Filter> getFilters() {\n      Map<String, Filter> filters = new HashMap<>();\n      filters.put(\"date\", new CustomDateFilter());\n      return filters;\n    }\n\n    @Override\n    public List<BinaryOperator> getBinaryOperators() {\n      BinaryOperatorImpl equalsOperator = new BinaryOperatorImpl(\n          \"==\", 30, FakeEqualsExpression.class, Associativity.LEFT);\n\n      return singletonList(equalsOperator);\n    }\n\n    @Override\n    public List<UnaryOperator> getUnaryOperators() {\n      UnaryOperatorImpl equalsOperator = new UnaryOperatorImpl(\n          \"not\", 500, FakeUnaryNotExpression.class);\n\n      return singletonList(equalsOperator);\n    }\n  }\n\n  private static class CustomI18nFunction implements Function {\n\n    @Override\n    public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n        int lineNumber) {\n      return \"custom i18n function\";\n    }\n\n    @Override\n    public List<String> getArgumentNames() {\n      return null;\n    }\n  }\n\n  public static class FakeEqualsExpression extends BinaryExpression<String> {\n\n    @Override\n    public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n      return \"custom binary operator\";\n    }\n  }\n\n  public static class FakeUnaryNotExpression extends UnaryExpression {\n\n    @Override\n    public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) {\n      return \"custom unary operator\";\n    }\n  }\n\n  private static class CustomDateFilter implements Filter {\n\n    @Override\n    public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n        EvaluationContext context, int lineNumber) throws PebbleException {\n      return \"custom date filter\";\n    }\n\n    @Override\n    public List<String> getArgumentNames() {\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ParsingOdditiesTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass ParsingOdditiesTest {\n\n  @Test\n  void testEscapeCharactersText() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"templates/template.escapeCharactersInText.peb\");\n    Map<String, Object> context = new HashMap<>();\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n  }\n\n  @Test\n  void testExpressionInArguments() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ input(1 + 1) }}{% macro input(value) %}{{value}}{% endmacro %}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"2\", writer.toString());\n  }\n\n  @Test\n  void testPositionalAndNamedArguments()\n      throws PebbleException, IOException, ParseException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .defaultLocale(Locale.ENGLISH).build();\n\n    String source = \"{{ stringDate | date('yyyy/MMMM/d', existingFormat='yyyy-MMMM-d') }}\";\n\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> context = new HashMap<>();\n    DateFormat format = new SimpleDateFormat(\"yyyy-MMMM-d\", Locale.ENGLISH);\n    Date realDate = format.parse(\"2012-July-01\");\n    context.put(\"stringDate\", format.format(realDate));\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"2012/July/1\", writer.toString());\n  }\n\n  @Test\n  void testPositionalArgumentAfterNamedArguments() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false)\n          .defaultLocale(Locale.ENGLISH).build();\n\n      String source = \"{{ stringDate | date(existingFormat='yyyy-MMMM-d', 'yyyy/MMMM/d') }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testVariableNamePrefixedWithOperatorName() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ organization }} {{ nothing }} {{ andy }} {{ equalsy }} {{ istanbul }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"organization\", \"organization\");\n    context.put(\"nothing\", \"nothing\");\n    context.put(\"andy\", \"andy\");\n    context.put(\"equalsy\", \"equalsy\");\n    context.put(\"istanbul\", \"istanbul\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"organization nothing andy equalsy istanbul\", writer.toString());\n  }\n\n  @Test\n  void testAttributeNamePrefixedWithOperatorName() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ foo.org }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", new Foo(\"success\"));\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"success\", writer.toString());\n  }\n\n  public static class Foo {\n\n    public String org;\n\n    public Foo(String org) {\n      this.org = org;\n    }\n  }\n\n  @Test\n  void testIncorrectlyNamedArgument() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble\n          .getTemplate(\"{{ 'This is a test of the abbreviate filter' | abbreviate(WRONG=16) }}\");\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      assertEquals(\"This is a tes...\", writer.toString());\n    });\n  }\n\n  @Test\n  void testStringConstantWithLinebreak() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{ 'test\\ntest' }}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"test\\ntest\", writer.toString());\n  }\n\n  @Test\n  void testStringWithDifferentQuotationMarks() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{'test\\\"}}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testSingleQuoteWithinDoubleQuotes() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{\\\"te'st\\\"}}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"te'st\", writer.toString());\n\n    template = pebble.getTemplate(\"{{\\\"te\\\\'st\\\"}}\");\n    writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"te\\\\'st\", writer.toString());\n\n    template = pebble.getTemplate(\"{{'te\\\\'st'}}\");\n    writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"te'st\", writer.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/RenderSingleBlockTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static java.util.Locale.CANADA;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass RenderSingleBlockTest {\n\n  @Test\n  void testRenderSingleBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}Block A{% endblock %}{% block block_b %}Block B{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a);\n    assertEquals(\"Block A\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b);\n    assertEquals(\"Block B\", writer_b.toString());\n  }\n\n  @Test\n  void testRenderSingleBlockWithLocale() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}Block A{% endblock %}{% block block_b %}Block B{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a, CANADA);\n    assertEquals(\"Block A\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b, CANADA);\n    assertEquals(\"Block B\", writer_b.toString());\n  }\n\n  @Test\n  void testRenderSingleBlockWithContext() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}{{vara}}{% endblock %}{% block block_b %}{{varb}}{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"vara\", \"FOO\");\n    context.put(\"varb\", \"BAR\");\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a, context, CANADA);\n    assertEquals(\"FOO\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b, context, CANADA);\n    assertEquals(\"BAR\", writer_b.toString());\n  }\n\n  @Test\n  void testRenderSingleBlockWithContextAndLocale() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}{{vara}}{% endblock %}{% block block_b %}{{varb}}{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"vara\", \"FOO\");\n    context.put(\"varb\", \"BAR\");\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a, context);\n    assertEquals(\"FOO\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b, context);\n    assertEquals(\"BAR\", writer_b.toString());\n  }\n\n  @Test\n  void testRenderSingleExtendedBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/single-block/template.renderextendedblock1.peb\");\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"container_a\", writer_a);\n    assertEquals(\"Block A extended\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"container_b\", writer_b);\n    assertEquals(\"Block B extended\", writer_b.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/RenderWithoutEndBlockTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static java.util.Locale.CANADA;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass RenderWithoutEndBlockTest {\n\n  @Test\n  void testRenderWithoutEndBlock() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"Prefix {% block block_a %}Block A{% block block_b %}Block B{% endblock %} Postfix\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Writer writer_a = new StringWriter();\n      template.evaluateBlock(\"block_a\", writer_a);\n    });\n  }\n\n  @Test\n  void testRenderWithEndBlock() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}Block A{% endblock %}{% block block_b %}Block B{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a);\n    assertEquals(\"Block A\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b);\n    assertEquals(\"Block B\", writer_b.toString());\n\n  }\n\n  @Test\n  void testRenderWithoutEndBlockWithLocale() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"Prefix {% block block_a %}Block A{% block block_b %}Block B{% endblock %} Postfix\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Writer writer_a = new StringWriter();\n      template.evaluateBlock(\"block_a\", writer_a, CANADA);\n      assertEquals(\"Block A\", writer_a.toString());\n\n      Writer writer_b = new StringWriter();\n      template.evaluateBlock(\"block_b\", writer_b, CANADA);\n      assertEquals(\"Block B\", writer_b.toString());\n    });\n  }\n\n  @Test\n  void testRenderWithEndBlockWithLocale() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}Block A{% endblock %}{% block block_b %}Block B{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a, CANADA);\n    assertEquals(\"Block A\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b, CANADA);\n    assertEquals(\"Block B\", writer_b.toString());\n  }\n\n  @Test\n  void testRenderWithoutEndBlockWithContext() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"Prefix {% block block_a %}{{vara}}{% endblock %}{% block block_b %}{{varb}} Postfix\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"vara\", \"FOO\");\n      context.put(\"varb\", \"BAR\");\n\n      Writer writer_a = new StringWriter();\n      template.evaluateBlock(\"block_a\", writer_a, context, CANADA);\n      assertEquals(\"FOO\", writer_a.toString());\n\n      Writer writer_b = new StringWriter();\n      template.evaluateBlock(\"block_b\", writer_b, context, CANADA);\n      assertEquals(\"BAR\", writer_b.toString());\n    });\n  }\n\n  @Test\n  void testRenderEndBlockWithContext() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}{{vara}}{% endblock %}{% block block_b %}{{varb}}{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"vara\", \"FOO\");\n    context.put(\"varb\", \"BAR\");\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a, context, CANADA);\n    assertEquals(\"FOO\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b, context, CANADA);\n    assertEquals(\"BAR\", writer_b.toString());\n  }\n\n\n  @Test\n  void testRenderWithoutEndBlockWithContextAndLocale() throws PebbleException, IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"Prefix {% block block_a %}{{vara}}{% block block_b %}{{varb}}{% endblock %} Postfix\";\n      PebbleTemplate template = pebble.getTemplate(source);\n\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"vara\", \"FOO\");\n      context.put(\"varb\", \"BAR\");\n\n      Writer writer_a = new StringWriter();\n      template.evaluateBlock(\"block_a\", writer_a, context);\n      assertEquals(\"FOO\", writer_a.toString());\n\n      Writer writer_b = new StringWriter();\n      template.evaluateBlock(\"block_b\", writer_b, context);\n      assertEquals(\"BAR\", writer_b.toString());\n    });\n  }\n\n  @Test\n  void testRenderWithEndBlockWithContextAndLocale() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"Prefix {% block block_a %}{{vara}}{% endblock %}{% block block_b %}{{varb}}{% endblock %} Postfix\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"vara\", \"FOO\");\n    context.put(\"varb\", \"BAR\");\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"block_a\", writer_a, context);\n    assertEquals(\"FOO\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"block_b\", writer_b, context);\n    assertEquals(\"BAR\", writer_b.toString());\n  }\n\n  @Test\n  void testRenderWithoutEndBlockTest() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/single-block/template.renderextendedblock1.peb\");\n\n    Writer writer_a = new StringWriter();\n    template.evaluateBlock(\"container_a\", writer_a);\n    assertEquals(\"Block A extended\", writer_a.toString());\n\n    Writer writer_b = new StringWriter();\n    template.evaluateBlock(\"container_b\", writer_b);\n    assertEquals(\"Block B extended\", writer_b.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ScopeChainTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.ScopeChain;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass ScopeChainTest {\n\n  @Test\n  void testSet() throws PebbleException {\n    ScopeChain scopeChain = new ScopeChain();\n    scopeChain.pushScope();\n    scopeChain.set(\"key\", \"value\");\n    assertEquals(\"value\", scopeChain.get(\"key\"));\n    scopeChain.pushScope();\n    scopeChain.set(\"key\", \"value2\");\n    assertEquals(\"value2\", scopeChain.get(\"key\"));\n    scopeChain.popScope();\n    assertEquals(\"value2\", scopeChain.get(\"key\"));\n    scopeChain.pushLocalScope();\n    scopeChain.set(\"key\", \"value3\");\n    assertEquals(\"value3\", scopeChain.get(\"key\"));\n    scopeChain.popScope();\n    assertEquals(\"value2\", scopeChain.get(\"key\"));\n  }\n\n  @Test\n  void testGetValueWithLocalScopeFirstInChainAndValueInAnotherScope() {\n    ScopeChain scopeChain = new ScopeChain();\n    scopeChain.pushScope();\n    scopeChain.set(\"key\", \"value\");\n\n    scopeChain.pushLocalScope();\n    scopeChain.set(\"key2\", \"value2\");\n\n    assertNull(scopeChain.get(\"key\"));\n    assertEquals(\"value2\", scopeChain.get(\"key2\"));\n  }\n\n  @Test\n  void testGetValueWithLocalScopeNotFirstInChainAndValueInAnotherScope() {\n    ScopeChain scopeChain = new ScopeChain();\n    scopeChain.pushScope();\n    scopeChain.set(\"key\", \"value\");\n\n    scopeChain.pushLocalScope();\n    scopeChain.set(\"key2\", \"value2\");\n\n    scopeChain.pushScope();\n    scopeChain.set(\"key3\", \"value3\");\n\n    assertNull(scopeChain.get(\"key\"));\n    assertEquals(\"value2\", scopeChain.get(\"key2\"));\n    assertEquals(\"value3\", scopeChain.get(\"key3\"));\n  }\n\n  @Test\n  void testContainsKeyWithLocalScopeFirstInChainAndValueInAnotherScope() {\n    ScopeChain scopeChain = new ScopeChain();\n    scopeChain.pushScope();\n    scopeChain.set(\"key\", \"value\");\n\n    scopeChain.pushLocalScope();\n    scopeChain.set(\"key2\", \"value2\");\n\n    assertFalse(scopeChain.containsKey(\"key\"));\n    assertTrue(scopeChain.containsKey(\"key2\"));\n  }\n\n  @Test\n  void testContainsKeyWithLocalScopeNotFirstInChainAndValueInAnotherScope() {\n    ScopeChain scopeChain = new ScopeChain();\n    scopeChain.pushScope();\n    scopeChain.set(\"key\", \"value\");\n\n    scopeChain.pushLocalScope();\n    scopeChain.set(\"key2\", \"value2\");\n\n    scopeChain.pushScope();\n    scopeChain.set(\"key3\", \"value3\");\n\n    assertFalse(scopeChain.containsKey(\"key\"));\n    assertTrue(scopeChain.containsKey(\"key2\"));\n    assertTrue(scopeChain.containsKey(\"key3\"));\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/ScopeTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.template.Scope;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass ScopeTest {\n\n    @Test\n    void testGetKeys() {\n        Map<String, Object> map = new HashMap<>();\n        map.putIfAbsent(\"key1\", new String(\"value1\"));\n        map.putIfAbsent(\"key2\", new String(\"value2\"));\n\n        Scope scope = new Scope(map, false);\n        Set<String> expected = new HashSet<>();\n        expected.add(\"key1\");\n        expected.add(\"key2\");\n\n        assertEquals(expected, scope.getKeys());\n    }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/SplitFilterTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass SplitFilterTest {\n\n  @Test\n  void whenSplit_givenInputNull_thenReturnNull() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% set foo = null | split(',') %}\\n\"\n        + \"{{ foo }}\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"\", writer.toString());\n  }\n\n  @Test\n  void whenSplit_givenNoDelimiter_thenThrowPebbleException() throws IOException {\n    assertThrows(PebbleException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      PebbleTemplate template = pebble.getTemplate(\"{{ \\\"one,two,three\\\" | split }}\");\n\n      Map<String, Object> context = new HashMap<>();\n\n      Writer writer = new StringWriter();\n      template.evaluate(writer, context);\n    });\n  }\n\n  @Test\n  void whenSplit_givenInputWithDelimiter_thenSplit() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{% set foo = 'one|two|three' | split('\\\\|') %}\"\n        + \"{% for var in foo %}\"\n        + \"{{ var }}\"\n        + \"{% endfor %}\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"onetwothree\", writer.toString());\n  }\n\n  @Test\n  void whenSplit_givenInputWithDelimiterAndPositiveLimit_thenSplit() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% set foo = \\\"one,two,three,four,five\\\" | split(',',3) %}\"\n            + \"{% for var in foo %}\"\n            + \"{{ var }}\"\n            + \"{% endfor %}\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"onetwothree,four,five\", writer.toString());\n  }\n\n  @Test\n  void whenSplit_givenInputWithDelimiterAndNegativeLimit_thenSplit() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% set foo = \\\"one,two,three,four,five\\\" | split(',',-1) %}\"\n            + \"{% for var in foo %}\"\n            + \"{{ var }}\"\n            + \"{% endfor %}\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"onetwothreefourfive\", writer.toString());\n  }\n\n  @Test\n  void whenSplit_givenInputWithDelimiterAndZeroLimit_thenSplit() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% set foo = \\\"one,two,three,four,five\\\" | split(',',0) %}\"\n            + \"{% for var in foo %}\"\n            + \"{{ var }}\"\n            + \"{% endfor %}\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n\n    assertEquals(\"onetwothreefourfive\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/StrictModeTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.error.RootAttributeNotFoundException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n/**\n * Tests if strict mode works in any case.\n *\n * @author Thomas Hunziker\n */\nclass StrictModeTest {\n\n\n  /**\n   * Tests that the line number and file name is correctly passed to the exception in strict mode.\n   */\n  @Test()\n  void testComplexVariable() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/template.strictModeComplexExpression.peb\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n\n    try {\n      template.evaluate(writer, context);\n      fail(\"Exception \" + RootAttributeNotFoundException.class.getCanonicalName() + \" is expected.\");\n    } catch (RootAttributeNotFoundException e) {\n      assertEquals(e.getFileName(), \"templates/template.strictModeComplexExpression.peb\");\n      assertEquals(e.getLineNumber(), (Integer) 2);\n    }\n  }\n\n  /**\n   * Tests that the line number and file name is correctly passed to the exception in strict mode.\n   */\n  @Test()\n  void testSimpleVariable() throws PebbleException, IOException {\n\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/template.strictModeSimpleExpression.peb\");\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n\n    try {\n      template.evaluate(writer, context);\n      fail(\n          \"Exception \" + RootAttributeNotFoundException.class.getCanonicalName() + \" is expected.\");\n    } catch (RootAttributeNotFoundException e) {\n      assertEquals(\"templates/template.strictModeSimpleExpression.peb\", e.getFileName());\n      assertEquals((Integer) 2, e.getLineNumber());\n    }\n  }\n\n  @Test\n  void whenStrictVariableEnabledWithAndExpressionAndLeftOperandFalse_thenDontEvaluateRightExpression()\n      throws PebbleException, IOException {\n    PebbleEngine engine = new PebbleEngine\n        .Builder()\n        .loader(new StringLoader())\n        .strictVariables(true)\n        .autoEscaping(false)\n        .build();\n\n    PebbleTemplate template = engine.getTemplate(\"{%- set a = null -%}\\n\" +\n        \"{{- a is not null and a.toLowerCase() == \\\"abc\\\" -}}\\n\" +\n        \"{%- if a is not null and a.toLowerCase() == \\\"abc\\\" -%}\\n\" +\n        \"Do something\" +\n        \"{%- endif -%}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void whenStrictVariableEnabledWithOrExpressionAndLeftOperandTrue_thenDontEvaluateRightExpression()\n      throws PebbleException, IOException {\n    PebbleEngine engine = new PebbleEngine\n        .Builder()\n        .loader(new StringLoader())\n        .strictVariables(true)\n        .autoEscaping(false)\n        .build();\n\n    PebbleTemplate template = engine.getTemplate(\"{%- set a = null -%}\\n\" +\n        \"{{- a is null or a.toLowerCase() == \\\"abc\\\" -}}\\n\" +\n        \"{%- if a is null or a.toLowerCase() == \\\"abc\\\" -%}\\n\" +\n        \"Do something\" +\n        \"{%- endif -%}\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"trueDo something\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/StringInterpolationTest.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass StringInterpolationTest {\n\n  @Test\n  void testSimpleVariableInterpolation() throws Exception {\n    String source = \"{{ \\\"Hello, #{name}\\\" }}\";\n    Map<String, Object> ctx = new HashMap<>();\n    ctx.put(\"name\", \"joe\");\n    assertEquals(\"Hello, joe\", this.evaluate(source, ctx));\n  }\n\n  @Test\n  void testExpressionInterpolation() throws Exception {\n    String src = \"{{ \\\"1 plus 2 equals #{1 + 2}\\\" }}\";\n    assertEquals(\"1 plus 2 equals 3\", this.evaluate(src));\n  }\n\n  @Test\n  void testUnclosedInterpolation() throws Exception {\n    assertThrows(PebbleException.class, () -> {\n      String src = \"{{ \\\" #{ 1 +\\\" }}\";\n      this.evaluate(src);\n    });\n  }\n\n  @Test\n  void testDoubleClosedInterpolation() throws Exception {\n    String src = \"{{ \\\"#{3}}\\\" }}\";\n    assertEquals(\"3}\", this.evaluate(src));\n  }\n\n  @Test\n  void testFunctionInInterpolation() throws Exception {\n    String src = \"{{ \\\"Maximum: #{ max(5, 10) } \\\" }}\";\n    assertEquals(\"Maximum: 10 \", this.evaluate(src));\n  }\n\n  @Test\n  void testVerbatimInterpolation() throws Exception {\n    String src = \"{% verbatim %}{{ \\\"Sum: #{ 1 + 2 }\\\" }}{% endverbatim %}\";\n    assertEquals(\"{{ \\\"Sum: #{ 1 + 2 }\\\" }}\", this.evaluate(src));\n  }\n\n  @Test\n  void testInterpolationWithEscapedQuotes() throws Exception {\n    String str = \"{{ \\\"The cow says: #{\\\"\\\\\\\"moo\\\\\\\"\\\"}\\\" }}\";\n    assertEquals(\"The cow says: \\\"moo\\\"\", this.evaluate(str));\n  }\n\n  @Test\n  void testNestedInterpolation0() throws Exception {\n    String src = \"{{ \\\"Nested: #{ outer + \\\" #{ inner }\\\" }\\\" }}\";\n    Map<String, Object> ctx = new HashMap<>();\n    ctx.put(\"outer\", \"OUTER\");\n    ctx.put(\"inner\", \"INNER\");\n    assertEquals(\"Nested: OUTER INNER\", this.evaluate(src, ctx));\n  }\n\n  @Test\n  void testNestedInterpolation1() throws Exception {\n    String src = \"{{ \\\"#{\\\"#{\\\"#{'hi'}\\\"}\\\"}\\\" }}\";\n    assertEquals(\"hi\", this.evaluate(src));\n  }\n\n  @Test\n  void testInterpolationWhitespace0() throws Exception {\n    String src = \"{{ \\\"Outer: #{3+4}\\\" }}\";\n    assertEquals(\"Outer: 7\", this.evaluate(src));\n  }\n\n  @Test\n  void testInterpolationWhitespace1() throws Exception {\n    String src = \"{{ \\\"Outer: #{ 3 + 4 }\\\" }}\";\n    assertEquals(\"Outer: 7\", this.evaluate(src));\n  }\n\n  @Test\n  void testInterpolationWhitespace2() throws Exception {\n    String src = \"{{ \\\"Outer:#{ 3 + 4 }\\\" }}\";\n    assertEquals(\"Outer:7\", this.evaluate(src));\n  }\n\n  @Test\n  void testInterpolationWhitespace3() throws Exception {\n    String src = \"{{ \\\"Outer:#{ 3 + 4 } \\\" }}\";\n    assertEquals(\"Outer:7 \", this.evaluate(src));\n  }\n\n  @Test\n  void testInterpolationWhitespace4() throws Exception {\n    String src = \"{{ \\\"Outer:  #{ 3 + 4 }  \\\" }}\";\n    assertEquals(\"Outer:  7  \", this.evaluate(src));\n  }\n\n  @Test\n  void testStringWithNumberSigns() throws Exception {\n    String src = \"{{ \\\"#bang #crash }!!\\\" }}\";\n    assertEquals(\"#bang #crash }!!\", this.evaluate(src));\n  }\n\n  @Test\n  void testStringWithNumberSignsAndInterpolation() throws Exception {\n    String src = \"{{ \\\"The cow said ##{'moo'}#\\\" }}\";\n    assertEquals(\"The cow said #moo#\", this.evaluate(src));\n  }\n\n  @Test\n  void testWhitespaceBetweenNumberSignAndCurlyBrace() throws Exception {\n    String src = \"{{ \\\"Green eggs and # {ham}\\\" }}\";\n    assertEquals(\"Green eggs and # {ham}\", this.evaluate(src));\n  }\n\n  @Test\n  void testStringInsideInterpolation() throws Exception {\n    String src = \"{{ \\\"Outer: #{ \\\"inner\\\" }\\\" }}\";\n    assertEquals(\"Outer: inner\", this.evaluate(src));\n  }\n\n  @Test\n  void testSingleQuoteNoInterpolation() throws Exception {\n    String src = \"{{ '#{3}'}}\";\n    assertEquals(\"#{3}\", this.evaluate(src));\n  }\n\n  @Test\n  void testSingleQuoteInsideInterpolation() throws Exception {\n    String src = \"{{ \\\"The cow says: #{'moo' + '#{moo}'}\\\" }}\";\n    assertEquals(\"The cow says: moo#{moo}\", this.evaluate(src));\n  }\n\n  @Test\n  void testSequentialInterpolations0() throws Exception {\n    String src = \"{{ \\\"#{1+1}#{2+2}\\\" }}\";\n    assertEquals(\"24\", this.evaluate(src));\n  }\n\n  @Test\n  void testSequentialInterpolations1() throws Exception {\n    String src = \"{{ \\\"The #{'cow'} says #{'moo'} and jumps #{'over'} the #{'moon'}\\\"}}\";\n    assertEquals(\"The cow says moo and jumps over the moon\", this.evaluate(src));\n  }\n\n  @Test\n  void testVariableContainingInterplationSyntax() throws Exception {\n    String src = \"{{ \\\"Hey #{name}\\\" }}\";\n    Map<String, Object> ctx = new HashMap<>();\n    ctx.put(\"name\", \"#{1+1}\");\n    assertEquals(\"Hey #{1+1}\", this.evaluate(src, ctx));\n  }\n\n  @Test\n  void testNewlineInInterpolation() throws Exception {\n    String src = \"{{ \\\"Sum = #{ 'egg\\negg'}\\\" }}\";\n    assertEquals(\"Sum = egg\\negg\", this.evaluate(src));\n  }\n\n  private String evaluate(String template) throws PebbleException, IOException {\n    return this.evaluate(template, null);\n  }\n\n  private String evaluate(String template, Map<String, Object> context)\n      throws PebbleException, IOException {\n    Writer writer = new StringWriter();\n\n    new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .build()\n        .getTemplate(template)\n        .evaluate(writer, context);\n    return writer.toString();\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/TernaryExpressionTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass TernaryExpressionTest {\n\n  @Test\n  void testTernaryFail1() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 'true' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail2() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? : 'true' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail3() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 'true' : }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail4() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? : }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail5() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? : ? 'true' : 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail6() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? true ? 'true' : 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail7() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? : false ? 'true' : 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail8() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 2 > 2 ? 'true' : 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail9() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 2 > 2 ? : 'false' : 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail10() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 2 > 2 ? : : 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail11() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 'true' : 3 > 3 ? 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail12() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 'true' : 3 > 3 ? : 'false' }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernaryFail13() throws PebbleException {\n    assertThrows(ParserException.class, () -> {\n      PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n          .strictVariables(false).build();\n\n      String source = \"{{ 1 > 1 ? 'true' : 3 > 3 ? : }}\";\n\n      pebble.getTemplate(source);\n    });\n  }\n\n  @Test\n  void testTernary1() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ 1 == 1 ? 'true' : 'false' }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testTernary2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ 1 > 1 ? 'true' : 'false' }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testTernary3() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ 1 > 1 ? true : false ? 2 > 2 ? 'a' : 'b' : 3 == 3 ? 'c' : 'd' }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"c\", writer.toString());\n  }\n\n  @Test\n  void testComplexTernary1() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ ('a' == 'b' ? 2 + 2 : (val - 2 is not even ? true : false) ) ? (min(otherVal,-1) | abs <  3 / 3 - 1 ? false : ['yay!'] contains 'yay!' ) : ('?' is not empty ? ''~'?' : 0) }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"val\", 3);\n    params.put(\"otherVal\", 100);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, params);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testComplexTernary2() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    String source = \"{{ 'a' == 'b' ? 2 + 2 : val - 2 is not even ? true : false ? min(otherVal,-1) | abs <  3 / 3 - 1 ? false : ['yay!'] contains 'yay!' : '?' is not empty ? ''~'?' : 0 }}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"val\", 3);\n    params.put(\"otherVal\", 100);\n    Writer writer = new StringWriter();\n    template.evaluate(writer, params);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testTernaryIntTrue() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ 1 ? 'true' : 'false' }}\");\n    StringWriter writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testTernaryIntFalse() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ 0 ? 'true' : 'false' }}\");\n    StringWriter writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testTernaryStringTrue() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ 'not empty' ? 'true' : 'false' }}\");\n    StringWriter writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testTernaryStringFalse() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ '' ? 'true' : 'false' }}\");\n    StringWriter writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"false\", writer.toString());\n  }\n\n  @Test\n  void testTernaryDecimalTrue() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ 0.000001 ? 'true' : 'false' }}\");\n    StringWriter writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"true\", writer.toString());\n  }\n\n  @Test\n  void testTernaryDecimalFalse() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    PebbleTemplate template = pebble.getTemplate(\n        \"{{ 0.00000 ? 'true' : 'false' }}\");\n    StringWriter writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"false\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/TestParallelParsing.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.NodeVisitor;\nimport io.pebbletemplates.pebble.lexer.Token;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.parser.Parser;\nimport io.pebbletemplates.pebble.node.RenderableNode;\nimport io.pebbletemplates.pebble.template.EvaluationContextImpl;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.template.PebbleTemplateImpl;\nimport io.pebbletemplates.pebble.tokenParser.TokenParser;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * This tests tests the parallel parsing / compilation of templates.\n *\n * @author Thomas Hunziker\n */\nclass TestParallelParsing {\n\n  /**\n   * Tests if the parse is working correctly within a multi threading environment.\n   */\n  @Test\n  void testParser() throws InterruptedException {\n    final PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true)\n        .extension(new DelayExtension()).build();\n\n    final AtomicReference<String> resultThread1 = new AtomicReference<>();\n    final AtomicReference<String> resultThread2 = new AtomicReference<>();\n\n    Thread thread1 = new Thread(() -> {\n      try {\n        PebbleTemplate template = pebble.getTemplate(\"templates/template.parallelParsing1.peb\");\n        Writer writer = new StringWriter();\n        template.evaluate(writer);\n        resultThread1.set(writer.toString());\n      } catch (PebbleException | IOException e) {\n        throw new RuntimeException(e);\n      }\n    });\n\n    Thread thread2 = new Thread(() -> {\n      try {\n        PebbleTemplate template = pebble.getTemplate(\"templates/template.parallelParsing2.peb\");\n        Writer writer = new StringWriter();\n        template.evaluate(writer);\n        resultThread2.set(writer.toString());\n      } catch (PebbleException | IOException e) {\n        throw new RuntimeException(e);\n      }\n    });\n\n    // Start the threads.\n    thread1.start();\n    thread2.start();\n\n    // Wait until both threads completed.\n    thread1.join();\n    thread2.join();\n\n    assertEquals(\"output in 1: a|output in 1: b|output in 1: c\", resultThread1.get());\n    assertEquals(\"output in 2: a|output in 2: b|output in 2: c\", resultThread2.get());\n  }\n\n  /**\n   * This extension provides a token parser which does introduce a delay during the parser. This\n   * allows to provoke failing of the test when the parallel implementation is not ok.\n   */\n  private static class DelayExtension extends AbstractExtension {\n\n    @Override\n    public List<TokenParser> getTokenParsers() {\n      return Collections.singletonList(new DelayTokenParser());\n    }\n\n  }\n\n  private static class DelayTokenParser implements TokenParser {\n\n    @Override\n    public String getTag() {\n      return \"delay\";\n    }\n\n    @Override\n    public RenderableNode parse(Token token, Parser parser) throws ParserException {\n\n      TokenStream stream = parser.getStream();\n\n      // skip over the 'delay' token\n      Token delayName = stream.next();\n\n      // expect a name or string for the new block\n      if (!delayName.test(Token.Type.NUMBER)) {\n\n        // we already know an error has occurred but let's just call the\n        // typical \"expect\" method so that we know a proper error\n        // message is given to user\n        stream.expect(Token.Type.NUMBER);\n      }\n\n      int delay = Integer.valueOf(delayName.getValue());\n\n      try {\n        // We sleep for the given number of milliseconds:\n        Thread.sleep(delay);\n      } catch (InterruptedException e) {\n        throw new RuntimeException(e);\n      }\n\n      // skip over the delay\n      stream.next();\n\n      stream.expect(Token.Type.EXECUTE_END);\n\n      return new RenderableNode() {\n\n        @Override\n        public void accept(NodeVisitor visitor) {\n          visitor.visit(this);\n        }\n\n        @Override\n        public void render(PebbleTemplateImpl self, Writer writer, EvaluationContextImpl context)\n            throws PebbleException {\n          // Do nothing.\n        }\n      };\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/TestRelativePath.java",
    "content": "package io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests if relative path works as expected.\n *\n * @author Thomas Hunziker\n */\nclass TestRelativePath {\n\n  /**\n   * Tests if relative includes work.\n   */\n  @Test\n  void testRelativeInclude() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/relativepath/template.relativeinclude1.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"included\", writer.toString());\n  }\n\n  /**\n   * Tests if relative extends work.\n   */\n  @Test\n  void testRelativeExtends() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/relativepath/template.relativeextends1.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"<div>overridden</div>\",\n        writer.toString().replaceAll(\"\\\\r?\\\\n\", \"\").replace(\"\\t\", \"\").replace(\" \", \"\"));\n  }\n\n  /**\n   * Tests if relative imports work.\n   */\n  @Test\n  void testRelativeImports() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().strictVariables(true).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"templates/relativepath/template.relativeimport1.peb\");\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(\"<input name=\\\"company\\\" value=\\\"forcorp\\\" type=\\\"text\\\" />\",\n        writer.toString().replaceAll(\"\\\\r?\\\\n\", \"\").replace(\"\\t\", \"\"));\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/WhitespaceControlTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass WhitespaceControlTest {\n\n  /**\n   * A windows newline character (i.e. \\n\\r) in a template should be recognized\n   * and output as as a Windows newline character. The Windows newline character\n   * should not be converted to a Unix newline character (i.e. \\n).\n   */\n  @Test\n  void testWindowsNewlineCharacter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate windowsTemplate = pebble.getTemplate(\"\\r\\n\");\n    Writer windowsWriter = new StringWriter();\n    windowsTemplate.evaluate(windowsWriter);\n    assertEquals(\"\\r\\n\", windowsWriter.toString());\n  }\n\n  /**\n   * A Unix newline character (i.e. \\n\\r) in a template should be recognized\n   * and output as as a Unix newline character. The Unix newline character\n   * should not be converted to a Windows newline character (i.e. \\r\\n).\n   */\n  @Test\n  void testUnixNewlineCharacter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate unixTemplate = pebble.getTemplate(\"\\n\");\n    Writer unixWriter = new StringWriter();\n    unixTemplate.evaluate(unixWriter);\n    assertEquals(\"\\n\", unixWriter.toString());\n  }\n\n  /**\n   * A leading Whitespace Control Modifier in an expression delimiter (i.e. Pebble variable reference)\n   * should remove whitespace before the variable reference on the same line up to any\n   * surrounding text, i.e. a print delimiter.\n   *\n   * @throws PebbleException\n   * @throws IOException\n   */\n  @Test\n  void testLeadingWhitespaceControlModifier() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"<li>    \t{{- foo }}</li>\");\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"bar\");\n    template.evaluate(writer, context);\n    assertEquals(\"<li>bar</li>\", writer.toString());\n  }\n\n  /**\n   * A trailing Whitespace Control Modifier in an expression delimiter (i.e. Pebble variable reference)\n   * should remove whitespace after the variable reference on the same line up to any\n   * surrounding text.\n   *\n   * @throws PebbleException\n   * @throws IOException\n   */\n  @Test\n  void testTrailingWhitespaceControlModifier() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"<li>{{ foo -}}   \t</li>\");\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"bar\");\n    template.evaluate(writer, context);\n    assertEquals(\"<li>bar</li>\", writer.toString());\n  }\n\n  /**\n   * A Whitespace Control Modifier in an expression delimiter (i.e. Pebble variable reference)\n   * should not have any effect if there is no whitespace immediately before or after the \n   * variable reference. \n   *\n   * @throws PebbleException\n   * @throws IOException\n   */\n  @Test\n  void testLeadingWhitespaceTrimWithoutOutsideText() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"{{- foo -}}\");\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"bar\");\n    template.evaluate(writer, context);\n    assertEquals(\"bar\", writer.toString());\n  }\n\n  /**\n   * A leading and trailing Whitespace Control Modifiers in an expression delimiter \n   * (i.e. Pebble variable reference) should remove whitespace before and after \n   * the variable reference on the same line up to any surrounding text.\n   *\n   * @throws PebbleException\n   * @throws IOException\n   */\n  @Test\n  void testLeadingAndTrailingWhitespaceControlModifier() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"{{ foo }} <li>   {{- foo -}}   \t</li> {{ foo }}\");\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"bar\");\n    template.evaluate(writer, context);\n    assertEquals(\"bar <li>bar</li> bar\", writer.toString());\n  }\n\n  /**\n   * Newline characters immediately before or after a Whitespace Control Modifier \n   * should be removed.\n   *\n   * @throws PebbleException\n   * @throws IOException\n   */\n  @Test\n  void testWhitespaceControlModifierRemovesNewlines() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"<li>\\n{{- foo -}}\\n</li>\");\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"bar\");\n    template.evaluate(writer, context);\n    assertEquals(\"<li>bar</li>\", writer.toString());\n  }\n\n  @Test\n  void testWhitespaceTrimWithExecuteDelimiter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"<li>    \t{%- if true %} success {% else %} fail {% endif -%}   \t</li>\");\n    Writer writer = new StringWriter();\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"foo\", \"bar\");\n    template.evaluate(writer, context);\n    assertEquals(\"<li> success </li>\", writer.toString());\n  }\n\n  @Test\n  void testLeadingWhitespaceTrimWithCommentDelimiter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"<li>    \t{#- comment #}</li>\");\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n    assertEquals(\"<li></li>\", writer.toString());\n  }\n\n  @Test\n  void testTrailingWhitespaceTrimWithCommentDelimiter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble.getTemplate(\"<li>{# comment -#}   \t</li>\");\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n    assertEquals(\"<li></li>\", writer.toString());\n  }\n\n  @Test\n  void testLeadingWhitespaceTrimWithVerbatimTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"<li> {%- verbatim %}{{ bar }} {%- endverbatim %}</li>\");\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n    assertEquals(\"<li>{{ bar }}</li>\", writer.toString());\n  }\n\n  @Test\n  void testTrailingWhitespaceTrimWithVerbatimTag() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(true).build();\n\n    PebbleTemplate template = pebble\n        .getTemplate(\"<li>{% verbatim -%} {{ bar }}{% endverbatim -%} </li>\");\n    Writer writer = new StringWriter();\n\n    template.evaluate(writer);\n    assertEquals(\"<li>{{ bar }}</li>\", writer.toString());\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/WritingTest.java",
    "content": "/*\n * This file is part of Pebble.\n * <p>\n * Copyright (c) 2014 by Mitchell Bösecke\n * <p>\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.Executors;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass WritingTest {\n\n  /**\n   * There was an issue where the pebble engine was closing the provided writer. This is wrong.\n   */\n  @Test\n  void testMultipleEvaluationsWithOneWriter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).build();\n\n    PebbleTemplate template1 = pebble.getTemplate(\"first\");\n    PebbleTemplate template2 = pebble.getTemplate(\"second\");\n\n    Writer writer = new UncloseableWriter();\n    template1.evaluate(writer);\n    template2.evaluate(writer);\n\n    assertEquals(\"firstsecond\", writer.toString());\n  }\n\n  public class UncloseableWriter extends StringWriter {\n\n    @Override\n    public void close() {\n      throw new RuntimeException(\"Can not close this writer.\");\n    }\n  }\n\n  /**\n   * The following test used to fail because one parallel thread would rewrite the contents of\n   * another parallel thread's character buffer.\n   */\n  @Test\n  void testParallelCharacterBuffersBeingOverriden() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .executorService(Executors.newCachedThreadPool()).build();\n    String source = \"beginning {% parallel %}{{ slowObject.first }}{% endparallel %} middle {% parallel %}{{ slowObject.second }}{% endparallel %} end {% parallel %}{{ slowObject.third }}{% endparallel %}\";\n    PebbleTemplate template = pebble.getTemplate(source);\n    for (int i = 0; i < 2; i++) {\n      Writer writer = new StringWriter();\n      Map<String, Object> context = new HashMap<>();\n      context.put(\"slowObject\", new SlowObject());\n      template.evaluate(writer, context);\n\n      assertEquals(\"beginning first middle second end third\", writer.toString());\n    }\n  }\n\n  public class SlowObject {\n\n    public String first() {\n      try {\n        Thread.sleep(200);\n      } catch (InterruptedException e) {\n        // TODO Auto-generated catch block\n        e.printStackTrace();\n      }\n      return \"first\";\n    }\n\n    public String second() {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        // TODO Auto-generated catch block\n        e.printStackTrace();\n      }\n      return \"second\";\n    }\n\n    public String third() {\n      try {\n        Thread.sleep(100);\n      } catch (InterruptedException e) {\n        // TODO Auto-generated catch block\n        e.printStackTrace();\n      }\n      return \"third\";\n    }\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/attributes/methodaccess/BlacklistMethodAccessValidatorTest.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.lang.reflect.Method;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n\nclass BlacklistMethodAccessValidatorTest {\n\n  private final InstanceProvider instanceProvider = new InstanceProvider();\n  private final MethodAccessValidator underTest = new BlacklistMethodAccessValidator();\n\n  @ParameterizedTest\n  @MethodSource(\"provideUnsafeMethod\")\n  void checkIfAccessIsForbidden(Method unsafeMethod)\n      throws NoSuchFieldException, NoSuchMethodException {\n    Class<?> declaringClass = unsafeMethod.getDeclaringClass();\n    Object instance = this.instanceProvider.createObject(declaringClass);\n\n    boolean methodAccessAllowed = this.underTest.isMethodAccessAllowed(instance, unsafeMethod);\n\n    assertThat(methodAccessAllowed).isFalse();\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"provideAllowedMethod\")\n  void checkIfAccessIsAllowed(Method allowedMethod) throws Throwable {\n    Class<?> declaringClass = allowedMethod.getDeclaringClass();\n    Object instance = this.instanceProvider.createObject(declaringClass);\n\n    boolean methodAccessAllowed = this.underTest.isMethodAccessAllowed(instance, allowedMethod);\n\n    assertThat(methodAccessAllowed).isTrue();\n  }\n\n  private static Stream<Method> provideUnsafeMethod() {\n    return MethodsProvider.UNSAFE_METHODS.stream();\n  }\n\n  private static Stream<Method> provideAllowedMethod() {\n    return MethodsProvider.ALLOWED_METHODS.stream();\n  }\n}"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/attributes/methodaccess/Foo.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\npublic class Foo {\n\n  private String x;\n\n  public void getX() {\n  }\n\n  private Foo() {\n  }\n\n  public void setX(String x) {\n    this.x = x;\n  }\n}"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/attributes/methodaccess/InstanceProvider.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\nimport java.lang.reflect.AccessibleObject;\nimport java.lang.reflect.Constructor;\n\npublic class InstanceProvider {\n\n  Object createObject(Class<?> declaringClass) throws NoSuchFieldException, NoSuchMethodException {\n    try {\n      Constructor<?> constructor = declaringClass.getDeclaredConstructor();\n      constructor.setAccessible(true);\n      return constructor.newInstance();\n    } catch (Exception e) {\n      switch (declaringClass.getName()) {\n        case \"java.lang.reflect.Field\":\n          return Foo.class.getDeclaredField(\"x\");\n        case \"java.lang.reflect.Method\":\n          return Foo.class.getDeclaredMethod(\"getX\", (Class<?>[]) null);\n        case \"java.lang.Class\":\n          return Foo.class;\n        case \"java.lang.reflect.Constructor\":\n          return Foo.class.getDeclaredConstructor();\n        case \"java.lang.Integer\":\n          return Integer.valueOf(1);\n        case \"java.lang.System\":\n          return System.class;\n        case \"java.lang.Runtime\":\n          return Runtime.class;\n        case \"java.lang.reflect.AccessibleObject\":\n          return AccessibleObject.class;\n        case \"java.lang.ThreadGroup\":\n          return Runtime.class;\n        default:\n          throw new RuntimeException(\n              String.format(\"No object instance defined for class %s\", declaringClass.getName()));\n      }\n    }\n  }\n}"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/attributes/methodaccess/MethodsProvider.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.StringTokenizer;\n\nclass MethodsProvider {\n\n  private static final String UNSAFE_METHODS_PROPERTIES = \"/security/unsafeMethods.properties\";\n  private static final String ALLOWED_METHODS_PROPERTIES = \"/security/allowedMethods.properties\";\n\n  static final Set<Method> UNSAFE_METHODS = createUnsafeMethodsSet();\n  static final Set<Method> ALLOWED_METHODS = createAllowedMethodsSet();\n\n  MethodsProvider() {\n  }\n\n  private static Set<Method> createAllowedMethodsSet() {\n    return createMethodsSet(ALLOWED_METHODS_PROPERTIES);\n  }\n\n  private static Set<Method> createUnsafeMethodsSet() {\n    return createMethodsSet(UNSAFE_METHODS_PROPERTIES);\n  }\n\n  private static Set<Method> createMethodsSet(String filename) {\n    try {\n      Properties props = loadProperties(filename);\n      Set<Method> set = new HashSet<>(props.size() * 4 / 3, 1f);\n      Map<String, Class> primClasses = createPrimitiveClassesMap();\n      for (Object key : props.keySet()) {\n        try {\n          set.add(parseMethodSpec((String) key, primClasses));\n        } catch (NoSuchMethodException | ClassNotFoundException ignored) {\n          // Ignore when the method is not found or the class is not found\n          // Continue to add other methods\n        }\n      }\n      return set;\n    } catch (Exception e) {\n      throw new RuntimeException(String.format(\"Could not load method set from file %s\", filename),\n          e);\n    }\n  }\n\n  private static Method parseMethodSpec(String methodSpec, Map<String, Class> primClasses)\n      throws ClassNotFoundException,\n      NoSuchMethodException {\n    int brace = methodSpec.indexOf('(');\n    int dot = methodSpec.lastIndexOf('.', brace);\n    Class clazz = Class.forName(methodSpec.substring(0, dot));\n    String methodName = methodSpec.substring(dot + 1, brace);\n    String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 1);\n    StringTokenizer tok = new StringTokenizer(argSpec, \",\");\n    int argcount = tok.countTokens();\n    Class[] argTypes = new Class[argcount];\n    for (int i = 0; i < argcount; i++) {\n      String argClassName = tok.nextToken();\n      argTypes[i] = primClasses.get(argClassName);\n      if (argTypes[i] == null) {\n        argTypes[i] = Class.forName(argClassName);\n      }\n    }\n    return clazz.getMethod(methodName, argTypes);\n  }\n\n  private static Map<String, Class> createPrimitiveClassesMap() {\n    Map<String, Class> map = new HashMap<>();\n    map.put(\"boolean\", Boolean.TYPE);\n    map.put(\"byte\", Byte.TYPE);\n    map.put(\"char\", Character.TYPE);\n    map.put(\"short\", Short.TYPE);\n    map.put(\"int\", Integer.TYPE);\n    map.put(\"long\", Long.TYPE);\n    map.put(\"float\", Float.TYPE);\n    map.put(\"double\", Double.TYPE);\n    return map;\n  }\n\n  private static Properties loadProperties(String resource) throws IOException {\n    Properties props = new Properties();\n    try (InputStream is = MethodsProvider.class.getResourceAsStream(resource)) {\n      props.load(is);\n    }\n    return props;\n  }\n}"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/attributes/methodaccess/NoOpMethodAccessValidatorTest.java",
    "content": "package io.pebbletemplates.pebble.attributes.methodaccess;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\n\npublic class NoOpMethodAccessValidatorTest {\n\n  private final MethodAccessValidator underTest = new NoOpMethodAccessValidator();\n\n  @Test\n  void whenIsMethodAccessAllowed_thenReturnTrue() {\n    boolean methodAccessAllowed = this.underTest.isMethodAccessAllowed(null, null);\n\n    assertThat(methodAccessAllowed).isTrue();\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/ArrayToStringFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.lang.reflect.Array;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Pretty-printing of java arrays\n */\npublic class ArrayToStringFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public String apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n\n    StringBuilder result = new StringBuilder(\"[\");\n    int length = Array.getLength(input);\n    for (int i = 0; i < length; i++) {\n      if (i > 0) {\n        result.append(\",\");\n      }\n      result.append(Array.get(input, i));\n    }\n    result.append(\"]\");\n\n    return result.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/ExtensionCustomizerTest.java",
    "content": "package io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.extension.core.DisallowExtensionCustomizerBuilder;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.StringWriter;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass ExtensionCustomizerTest {\n\n  @Test\n  void upperFilterCannotBeUsed() {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .registerExtensionCustomizer(RemoveUpperCustomizer::new)\n            .build();\n\n    Map<String, Object> obj = new HashMap<>();\n    obj.put(\"test\", \"abc\");\n    PebbleTemplate template = pebble.getTemplate(\"{{ test | upper }}\");\n\n    PebbleException exception = assertThrows(PebbleException.class, () -> template.evaluate(new StringWriter(), obj));\n    assertTrue(exception.getMessage().contains(\"upper\"),\n            () -> \"Expect upper-Filter to not exist, actual Problem: \" + exception.getMessage());\n  }\n\n  @Test\n  void setDisallowedTokenParserTags() {\n    PebbleEngine pebbleEngine = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()\n                    .disallowedTokenParserTags(Collections.singletonList(\"flush\"))\n                    .build())\n            .build();\n\n    PebbleException exception = assertThrows(PebbleException.class,\n            () -> pebbleEngine.getTemplate(\"{{ k1 }}\\n\" +\n                    \"{% flush %}\\n\" +\n                    \"{{ k2 }}\"));\n    assertTrue(exception.getMessage().contains(\"Unexpected tag name \\\"flush\\\"\"));\n  }\n\n  @Test\n  void setDisallowedFilters() {\n    PebbleEngine pebbleEngine = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()\n                    .disallowedFilterKeys(Collections.singletonList(\"upper\"))\n                    .build())\n            .build();\n\n    Map<String, Object> obj = new HashMap<>();\n    obj.put(\"test\", \"abc\");\n    PebbleTemplate template = pebbleEngine.getTemplate(\"{{ test | upper }}\");\n\n    PebbleException exception = assertThrows(PebbleException.class, () -> template.evaluate(new StringWriter(), obj));\n    assertTrue(exception.getMessage().contains(\"upper\"),\n            () -> \"Expect upper-Filter to not exist, actual Problem: \" + exception.getMessage());\n  }\n\n  @Test\n  void setDisallowedFunctions() {\n    PebbleEngine pebbleEngine = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()\n                    .disallowedFunctionKeys(Collections.singletonList(\"max\"))\n                    .build())\n            .build();\n\n    Map<String, Object> obj = new HashMap<>();\n    obj.put(\"age\", 30);\n    PebbleTemplate template = pebbleEngine.getTemplate(\"{{ max(age, 80) }}\");\n\n    PebbleException exception = assertThrows(PebbleException.class, () -> template.evaluate(new StringWriter(), obj));\n    assertTrue(exception.getMessage().contains(\"Function or Macro [max] does not exist\"));\n  }\n\n  @Test\n  void setDisallowedBinaryOperatorSymbols() {\n    PebbleEngine pebbleEngine = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()\n                    .disallowedBinaryOperatorSymbols(Collections.singletonList(\">\"))\n                    .build())\n            .build();\n\n    PebbleException exception = assertThrows(PebbleException.class, () -> pebbleEngine.getTemplate(\"{% if 10 > 9 %}\\n\" +\n            \"{{ name }}\" +\n            \"{% endif %}\"));\n    assertTrue(exception.getMessage().contains(\"Unexpected character [>]\"));\n  }\n\n  @Test\n  void setDisallowedUnaryOperatorSymbols() {\n    PebbleEngine pebbleEngine = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()\n                    .disallowedUnaryOperatorSymbols(Collections.singletonList(\"-\"))\n                    .build())\n            .build();\n\n    PebbleException exception = assertThrows(PebbleException.class, () -> pebbleEngine.getTemplate(\"{{ -num }}\"));\n    assertTrue(exception.getMessage().contains(\"Unexpected token \\\"OPERATOR\\\" of value \\\"-\\\"\"));\n  }\n\n  @Test\n  void setDisallowedTestKeys() {\n    PebbleEngine pebbleEngine = new PebbleEngine.Builder()\n            .loader(new StringLoader())\n            .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()\n                    .disallowedTestKeys(Collections.singletonList(\"null\"))\n                    .build())\n            .build();\n\n    PebbleTemplate template = pebbleEngine.getTemplate(\"{% if 123 is null %}\\n\" +\n            \"{{ name }}\" +\n            \"{% endif %}\");\n\n    PebbleException exception = assertThrows(PebbleException.class, () -> template.evaluate(new StringWriter()));\n    assertTrue(exception.getMessage().contains(\"Wrong operand(s) type in conditional expression\"));\n  }\n\n  private static class RemoveUpperCustomizer extends ExtensionCustomizer {\n\n    public RemoveUpperCustomizer(Extension core) {\n      super(core);\n    }\n\n    @Override\n    public Map<String, Filter> getFilters() {\n      Map<String, Filter> filters = Optional.ofNullable(super.getFilters()).map(HashMap::new)\n              .orElseGet(HashMap::new);\n      filters.remove(\"upper\");\n      return filters;\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/InvocationCountingFunction.java",
    "content": "package io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * This function will count how many times it's been invoked (just testing purposes). Not thread\n * safe.\n *\n * @author mbosecke\n */\npublic class InvocationCountingFunction implements Function {\n\n  private int invocationCount = 0;\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    return ++invocationCount;\n  }\n\n  public int getInvocationCount() {\n    return invocationCount;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/ListToStringFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Pretty-printing of List, implementation independent\n */\npublic class ListToStringFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @Override\n  public String apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) throws PebbleException {\n    if (input == null) {\n      return null;\n    }\n\n    if (!(input instanceof List)) {\n      throw new PebbleException(null,\n          \"The 'listToString' filter expects that the input to be a list.\", lineNumber,\n          self.getName());\n    }\n\n    List<?> inputList = (List<?>) input;\n    StringBuilder result = new StringBuilder(\"[\");\n    for (int i = 0; i < inputList.size(); i++) {\n      if (i > 0) {\n        result.append(\",\");\n      }\n      result.append(inputList.get(i));\n    }\n    result.append(\"]\");\n\n    return result.toString();\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/MapToStringFilter.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.extension;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n/**\n * There were changes in how HashMaps are serialized to strings between Java 7 and 8 so this filter\n * ensures a standardized result that we can test with.\n *\n * @author mbosecke\n */\npublic class MapToStringFilter implements Filter {\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n\n  @SuppressWarnings({\"unchecked\"})\n  @Override\n  public String apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    if (input == null) {\n      return null;\n    }\n    Map<Object, Object> map = (Map<Object, Object>) input;\n\n    List<String> pairs = new ArrayList<>();\n\n    for (Entry<Object, Object> entry : map.entrySet()) {\n      String pair = String.valueOf(entry.getKey()) + \"=\" + String.valueOf(entry.getValue());\n      pairs.add(pair);\n    }\n\n    Collections.sort(pairs);\n\n    StringBuilder result = new StringBuilder(\"{\");\n    for (String pair : pairs) {\n      result.append(pair).append(\", \");\n    }\n    result.setLength(result.length() - 2); // remove extra comma and\n    // space\n    result.append(\"}\");\n\n    return result.toString();\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/TestingExtension.java",
    "content": "package io.pebbletemplates.pebble.extension;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class TestingExtension extends AbstractExtension {\n\n  private InvocationCountingFunction invocationCountingFunction = new InvocationCountingFunction();\n\n  @Override\n  public Map<String, Function> getFunctions() {\n    Map<String, Function> functions = new HashMap<>();\n    functions.put(\"invocationCountingFunction\", this.invocationCountingFunction);\n    return functions;\n  }\n\n  @Override\n  public Map<String, Filter> getFilters() {\n    Map<String, Filter> filters = new HashMap<>();\n    filters.put(\"mapToString\", new MapToStringFilter());\n    filters.put(\"listToString\", new ListToStringFilter());\n    filters.put(\"arrayToString\", new ArrayToStringFilter());\n    return filters;\n  }\n\n  public InvocationCountingFunction getInvocationCountingFunction() {\n    return this.invocationCountingFunction;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/core/FormatFilterTest.java",
    "content": "package io.pebbletemplates.pebble.extension.core;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.MissingFormatArgumentException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\n\nclass FormatFilterTest {\n\n    @Test\n    void itShouldThrowExceptionWhenNotEnoughArguments() {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'I need %s and %s and %s' | format('one', 'two') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n\n        assertThatExceptionOfType(MissingFormatArgumentException.class)\n            .isThrownBy(() -> template.evaluate(writer, new HashMap<>()));\n    }\n\n    @Test\n    void itShouldReturnNullWhenInputIsNull() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ null | format('test') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEmpty();\n    }\n\n    @Test\n    void itShouldHandleNoArguments() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'Just a string with no placeholders' | format() }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"Just a string with no placeholders\");\n    }\n\n    @Test\n    void itShouldFormatStringWithSingleArgument() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'Hello %s!' | format('World') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"Hello World!\");\n    }\n\n\n\n    @Test\n    void itShouldFormatStringWithVariable() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{% set name = 'World' %}{{ 'Hello %s!' | format(name) }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"Hello World!\");\n    }\n\n    @Test\n    void itShouldFormatWithDifferentTypes() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'Number: %d, Float: %.2f, String: %s' | format(42, 3.14159, 'test') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"Number: 42, Float: 3.14, String: test\");\n    }\n\n    @Test\n    void itShouldIgnoreExtraArguments() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'I only need %s' | format('one', 'two', 'three') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"I only need one\");\n    }\n\n    @Test\n    void itShouldHandleNullArgument() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'Value is: %s' | format(null) }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"Value is: null\");\n    }\n\n    @Test\n    void itShouldHandleEscapedPercentSign() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ '100%% complete: %s' | format('done') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"100% complete: done\");\n    }\n\n    @Test\n    void itShouldFormatWithBooleanValues() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'Enabled: %s, Disabled: %s' | format(true, false) }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"Enabled: true, Disabled: false\");\n    }\n\n    @Test\n    void itShouldFormatEmptyString() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ '' | format('ignored') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEmpty();\n    }\n\n    @Test\n    void itShouldFormatWithMixedArgumentTypes() throws PebbleException, IOException {\n        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n            .strictVariables(false).build();\n\n        String source = \"{{ 'Count: %d items, Price: $%.2f, Name: %s' | format(5, 19.99, 'Widget') }}\";\n        PebbleTemplate template = pebble.getTemplate(source);\n\n        Writer writer = new StringWriter();\n        template.evaluate(writer, new HashMap<>());\n        assertThat(writer.toString()).isEqualTo(\"Count: 5 items, Price: $19.99, Name: Widget\");\n    }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/extension/escaper/RawFilterTest.java",
    "content": "package io.pebbletemplates.pebble.extension.escaper;\n\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass RawFilterTest {\n\n  @Test\n  void testRawFilter() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | upper | raw }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br />\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<BR />\", writer.toString());\n  }\n\n  @Test\n  void testRawFilterNotBeingLast() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | raw | upper}}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br />\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"&lt;BR /&gt;\", writer.toString());\n  }\n\n  @Test\n  void testRawFilterWithinAutoescapeToken() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false)\n        .autoEscaping(false).build();\n    PebbleTemplate template = pebble\n        .getTemplate(\"{% autoescape 'html' %}{{ text|raw }}{% endautoescape %}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", \"<br />\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"<br />\", writer.toString());\n  }\n\n  @Test\n  void testRawFilterWithJsonObject() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | raw }}\");\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"text\", new JsonObject());\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(JsonObject.JSON_VALUE, writer.toString());\n  }\n\n  @Test\n  void testRawFilterWithNullObject() throws PebbleException, IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n    PebbleTemplate template = pebble.getTemplate(\"{{ text | raw }}\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer, new HashMap<>());\n    assertEquals(\"\", writer.toString());\n  }\n\n  private class JsonObject {\n\n    public static final String JSON_VALUE = \"{\\\"menu\\\": {\\\"id\\\": \\\"file\\\",\\\"value\\\": \\\"File\\\",\\\"popup\\\": {\\\"menuitem\\\": [{\\\"value\\\": \\\"New\\\", \\\"onclick\\\": \\\"CreateNewDoc()\\\"},{\\\"value\\\": \\\"Open\\\", \\\"onclick\\\": \\\"OpenDoc()\\\"},{\\\"value\\\": \\\"Close\\\", \\\"onclick\\\": \\\"CloseDoc()\\\"}]}}}\";\n\n    @Override\n    public String toString() {\n      return JSON_VALUE;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/lexer/IdentifierTest.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.lexer;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.util.HashMap;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\npublic class IdentifierTest {\n\n  @Test\n  void common() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n    HashMap<String, Object> context = new HashMap<>();\n    context.put(\"fromContext\", \"ing\");\n    StringWriter writer = new StringWriter();\n    pebble.getTemplate(\"{% set hello1 = \\\"test\\\" %}{{ hello1 }}{{ fromContext }}\").evaluate(writer, context);\n    assertEquals(\"testing\", writer.toString());\n  }\n\n  @Test\n  void digitStart() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n    StringWriter writer = new StringWriter();\n    assertThrows(ParserException.class, () -> pebble.getTemplate(\"{{ 0digit }}\").evaluate(writer));\n  }\n\n  @Test\n  void extendedLatin() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n    HashMap<String, Object> context = new HashMap<>();\n    context.put(\"éabcêwçłë_0\", \"test string\");\n    StringWriter writer = new StringWriter();\n    pebble.getTemplate(\"{{ éabcêwçłë_0 }}\").evaluate(writer, context);\n    assertEquals(\"test string\", writer.toString());\n  }\n\n  @Test\n  void arabic() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n    HashMap<String, Object> context = new HashMap<>();\n    context.put(\"شششش\", \"test string\");\n    StringWriter writer = new StringWriter();\n    pebble.getTemplate(\"{{ شششش }}\").evaluate(writer, context);\n    assertEquals(\"test string\", writer.toString());\n  }\n\n  @Test\n  void chinese() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();\n    HashMap<String, Object> context = new HashMap<>();\n    context.put(\"水\", \"test string\");\n    StringWriter writer = new StringWriter();\n    pebble.getTemplate(\"{{ 水 }}\").evaluate(writer, context);\n    assertEquals(\"test string\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/lexer/LexerImplTest.java",
    "content": "package io.pebbletemplates.pebble.lexer;\n\nimport io.pebbletemplates.pebble.extension.ExtensionRegistry;\nimport io.pebbletemplates.pebble.extension.core.CoreExtension;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.pebble.loader.StringLoader;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.Reader;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass LexerImplTest {\n\n  @SuppressWarnings(\"unused\")\n  private final Logger logger = LoggerFactory.getLogger(LexerImplTest.class);\n\n  /**\n   * The Lexer Implementation under test.\n   */\n  private LexerImpl lexer;\n\n  // Don't really care what the template name is, but need to have it.\n  // Defined here to remove clutter from the tests\n  private final String TEMPLATE_NAME = \"Template Name\";\n\n  /**\n   * Configure and instantiate a Lexer instance before each test.\n   */\n  @BeforeEach\n  void setup() {\n    Syntax syntax = new Syntax.Builder().setEnableNewLineTrimming(false).build();\n\n    ExtensionRegistry extensionRegistry = new ExtensionRegistry();\n    extensionRegistry.addExtension(new CoreExtension());\n\n    this.lexer = new LexerImpl(syntax,\n        extensionRegistry.getUnaryOperators().values(),\n        extensionRegistry.getBinaryOperators().values());\n  }\n\n  /**\n   * Test Tokenizing text.\n   */\n  @Test\n  void testTokenizeText() {\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(\" template content \");\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    assertThat(tokenStream.peek(0).getType()).isEqualTo(Token.Type.TEXT);\n    assertThat(tokenStream.peek(0).getValue()).isEqualTo(\" template content \");\n    assertThat(tokenStream.peek(1).getType()).isEqualTo(Token.Type.EOF);\n  }\n\n  /**\n   * Test tokenizing an expression, e.g. {{ expression }}\n   */\n  @Test\n  void testTokenizeExpression() {\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(\"{{ whatever }}\");\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    assertThat(tokenStream.peek(0).getType()).isEqualTo(Token.Type.PRINT_START);\n    assertThat(tokenStream.peek(0).getValue()).isNull();\n\n    assertThat(tokenStream.peek(1).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(1).getValue()).isEqualTo(\"whatever\");\n\n    assertThat(tokenStream.peek(2).getType()).isEqualTo(Token.Type.PRINT_END);\n    assertThat(tokenStream.peek(2).getValue()).isEqualTo(\"}}\");\n\n    assertThat(tokenStream.peek(3).getType()).isEqualTo(Token.Type.EOF);\n    assertThat(tokenStream.peek(3).getValue()).isNull();\n  }\n\n  /**\n   * Test tokenizing an expression, e.g. {{ expression }}\n   */\n  @Test\n  void testVariableNameStartingWithOperator() {\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(\"{{ is_active + contains0 }}\");\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    int i = 0;\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.PRINT_START);\n    assertThat(tokenStream.peek(i++).getValue()).isNull();\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"is_active\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.OPERATOR);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"+\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"contains0\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.PRINT_END);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"}}\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EOF);\n    assertThat(tokenStream.peek(i++).getValue()).isNull();\n  }\n\n  /**\n   * Test tokenizing Punctuation, such as the dot in item.itemType\n   */\n  @Test\n  void testPunctuation() {\n\n    StringBuilder stringBuilder = new StringBuilder();\n    stringBuilder\n        .append(\"{% if item.itemType %}\");\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(stringBuilder.toString());\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    int i = 0;\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n    assertThat(tokenStream.peek(i++).getValue()).isNull();\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"if\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"item\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.PUNCTUATION);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\".\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"itemType\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EOF);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n  }\n\n  /**\n   * Test tokenizing an if statement that includes an operation and a String token\n   */\n  @Test\n  void testIfStatementWithOperatorAndStringToken() {\n\n    StringBuilder stringBuilder = new StringBuilder();\n    stringBuilder\n        .append(\"{% if item equals \\\"string1\\\" %}\");\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(stringBuilder.toString());\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    int i = 0;\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n    assertThat(tokenStream.peek(i++).getValue()).isNull();\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"if\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"item\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.OPERATOR);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"equals\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.STRING);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"string1\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n  }\n\n  /**\n   * Test tokenize an \"if\" statement\n   */\n  @Test\n  void testIfStatement() {\n\n    StringBuilder stringBuilder = new StringBuilder();\n    stringBuilder\n        .append(\"{% if item equals \\\"whatever\\\" %}\\n\")\n        .append(\"some text\\n\")\n        .append(\"{% endif %}\");\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(stringBuilder.toString());\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    int i = 0;\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n    assertThat(tokenStream.peek(i++).getValue()).isNull();\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"if\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"item\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.OPERATOR);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"equals\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.STRING);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"whatever\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.TEXT);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"\\nsome text\\n\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"endif\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EOF);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n  }\n\n  /**\n   * Test tokenizing a For Loop\n   */\n  @Test\n  void testTokenizeForLoop() {\n\n    StringBuilder stringBuilder = new StringBuilder();\n    stringBuilder\n        .append(\"{% for item in items %}\\n\")\n        .append(\"stuff\\n\")\n        .append(\"{% endfor %}\");\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(stringBuilder.toString());\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    assertThat(tokenStream.peek(0).getType()).isEqualTo(Token.Type.EXECUTE_START);\n    assertThat(tokenStream.peek(0).getValue()).isNull();\n\n    assertThat(tokenStream.peek(1).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(1).getValue()).isEqualTo(\"for\");\n\n    assertThat(tokenStream.peek(2).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(2).getValue()).isEqualTo(\"item\");\n\n    assertThat(tokenStream.peek(3).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(3).getValue()).isEqualTo(\"in\");\n\n    assertThat(tokenStream.peek(4).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(4).getValue()).isEqualTo(\"items\");\n\n    assertThat(tokenStream.peek(5).getType()).isEqualTo(Token.Type.EXECUTE_END);\n    assertThat(tokenStream.peek(5).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(6).getType()).isEqualTo(Token.Type.TEXT);\n\tassertThat(tokenStream.peek(6).getValue()).isEqualTo(\"\\nstuff\\n\");\n\t\n\tassertThat(tokenStream.peek(7).getType()).isEqualTo(Token.Type.EXECUTE_START);\n\tassertThat(tokenStream.peek(7).getValue()).isNull(); \n\t\n\tassertThat(tokenStream.peek(8).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(8).getValue()).isEqualTo(\"endfor\");\n\t\n\tassertThat(tokenStream.peek(9).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(9).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(10).getType()).isEqualTo(Token.Type.EOF);\n\tassertThat(tokenStream.peek(10).getValue()).isNull(); \n  }\n\n  /**\n   * Test tokenizing an if statement with a Whitespace Control character, i.e. the \"-\"\n   */\n  @Test\n  void testIfStatementWithWhitespaceControl() {\n\n    StringBuilder stringBuilder = new StringBuilder();\n    stringBuilder\n        .append(\"{% if item equals \\\"whatever\\\" -%}\\n\")\n        .append(\"some text\\n\")\n        .append(\"{%- endif %}\");\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(stringBuilder.toString());\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    int i = 0;\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n    assertThat(tokenStream.peek(i++).getValue()).isNull();\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"if\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"item\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.OPERATOR);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"equals\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.STRING);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"whatever\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.TEXT);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"some text\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"endif\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EOF);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n  }\n\n  /**\n   * Test a combination of template syntax to demonstrate a complex token stream\n   */\n  @Test\n  void testComplexTemplate() {\n\n    StringBuilder stringBuilder = new StringBuilder();\n    stringBuilder\n        .append(\"text before for loop followed by blank line\\n\")\n        .append(\"{% for item in items %}\\n\")\n        .append(\"{% if item.itemType equals \\\"ITEM_TYPE1\\\" -%}\\n\")\n        .append(\"Item 1\\n\")\n        .append(\"{% elseif item.itemType equals \\\"ITEM_TYPE2\\\" -%}\\n\")\n        .append(\"Item 2\\n\")\n        .append(\"{%- endif -%}\")\n        .append(\"{% endfor -%}\")\n        .append(\"text after for loop preceded by blank line\");\n    Loader<String> loader = new StringLoader();\n    Reader templateReader = loader.getReader(stringBuilder.toString());\n\n    TokenStream tokenStream = this.lexer.tokenize(templateReader, this.TEMPLATE_NAME);\n\n    int i = 0;\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.TEXT);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"text before for loop followed by blank line\\n\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n    assertThat(tokenStream.peek(i++).getValue()).isNull();\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"for\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"item\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"in\");\n\n    assertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n    assertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"items\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\t\n\t// note that the new line character included as part of a TEXT token\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.TEXT);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"\\n\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"if\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"item\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.PUNCTUATION);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\".\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"itemType\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.OPERATOR);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"equals\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.STRING);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"ITEM_TYPE1\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\"); \n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.TEXT);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"Item 1\\n\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"elseif\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"item\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.PUNCTUATION);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\".\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"itemType\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.OPERATOR);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"equals\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.STRING);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"ITEM_TYPE2\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.TEXT);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"Item 2\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"endif\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_START);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.NAME);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"endfor\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EXECUTE_END);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"%}\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.TEXT);\n\tassertThat(tokenStream.peek(i++).getValue()).isEqualTo(\"text after for loop preceded by blank line\");\n\t\n\tassertThat(tokenStream.peek(i).getType()).isEqualTo(Token.Type.EOF);\n\tassertThat(tokenStream.peek(i++).getValue()).isNull(); \n  }\n  \n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/lexer/SyntaxTest.java",
    "content": "package io.pebbletemplates.pebble.lexer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n// NOTE: Use regex101.com to test regular expressions through a web site\n\nclass SyntaxTest {\n\n  private static final String POSSIBLE_NEW_LINE = \"(\\r\\n|\\n\\r|\\r|\\n|\\u0085|\\u2028|\\u2029)?\";\n  @SuppressWarnings(\"unused\")\n  private final Logger logger = LoggerFactory.getLogger(SyntaxTest.class);\n\n  private Syntax syntax;\n  \n  @BeforeEach\n  void setup() {\n    this.syntax = new Syntax.Builder().build();\n  }\n\n  @Test\n  void testDelimiters() {\n    assertThat(this.syntax.getCommentOpenDelimiter()).isEqualTo(\"{#\");\n    \n    assertThat(this.syntax.getCommentCloseDelimiter()).isEqualTo(\"#}\");\n    \n    assertThat(this.syntax.getExecuteOpenDelimiter()).isEqualTo(\"{%\");\n    \n    assertThat(this.syntax.getExecuteCloseDelimiter()).isEqualTo(\"%}\");\n    \n    assertThat(this.syntax.getPrintOpenDelimiter()).isEqualTo(\"{{\");\n    \n    assertThat(this.syntax.getPrintCloseDelimiter()).isEqualTo(\"}}\");    \n    \n    assertThat(this.syntax.getInterpolationCloseDelimiter()).isEqualTo(\"}\");\n    \n    assertThat(this.syntax.getInterpolationOpenDelimiter()).isEqualTo(\"#{\");\n    \n    assertThat(this.syntax.getWhitespaceTrim()).isEqualTo(\"-\");\n  }\n \n  @Test\n  void tesCommentCloseDelimiter() {\n    assertThat(this.syntax.getCommentCloseDelimiter()).isEqualTo(\"#}\");\n  }\n  \n  @Test\n  void tesCommentOpenDelimiter() {\n    assertThat(this.syntax.getCommentOpenDelimiter()).isEqualTo(\"{#\");\n  }\n \n  @Test\n  void testExecuteOpenDelimiter() {    \n    assertThat(this.syntax.getExecuteOpenDelimiter()).isEqualTo(\"{%\");\n  }\n  \n  @Test\n  void testExecuteCloseDelimiter() {    \n    assertThat(this.syntax.getExecuteCloseDelimiter()).isEqualTo(\"%}\");\n  }\n  \n  @Test\n  void testPrintOpenDelimiter() {    \n    assertThat(this.syntax.getPrintCloseDelimiter()).isEqualTo(\"}}\");    \n  }\n \n  @Test\n  void testPrintCloseDelimiter() {    \n    assertThat(this.syntax.getPrintCloseDelimiter()).isEqualTo(\"}}\");    \n  }\n\n  @Test\n  void testInterpolationCloseDelimiter() {    \n    assertThat(this.syntax.getInterpolationCloseDelimiter()).isEqualTo(\"}\");\n  }\n\n  @Test\n  void testInterpolationOpenDelimiter() {    \n    assertThat(this.syntax.getInterpolationOpenDelimiter()).isEqualTo(\"#{\");\n  }\n\n  @Test\n  void testWhitespaceTrim() {    \n    assertThat(this.syntax.getWhitespaceTrim()).isEqualTo(\"-\");\n  }\n  \n  @Test\n  void testTrailingWhitespaceTrimRegex() {\n\n\tPattern pattern = syntax.getRegexTrailingWhitespaceTrim();\n    \n\t/*\n\t  matching from start of string, zero of more space characters, followed by a dash,\n\t  followed by one of the following: \"}}\", \"%}\", or \"#}\"\n\t */\n    String expectedPatternString = \"^\\\\s*\\\\Q-\\\\E(\\\\Q}}\\\\E|\\\\Q%}\\\\E|\\\\Q#}\\\\E)\";\n    // verify that the TrailingWhitepaceTrim regex in the Syntax class is the expected pattern string\n    assertThat(pattern.toString()).isEqualTo(expectedPatternString);\n    \n    StringBuilder templateText = null;\n    Matcher whitespaceTrimMatcher = null;\n    \n    // Whitespace Trim character with Execution Close Delimiter should match\n    templateText = new StringBuilder().append(\"-%}\");\n    whitespaceTrimMatcher = pattern.matcher(templateText);\n    assertThat(whitespaceTrimMatcher.lookingAt()).isEqualTo(true);\n    \n    // Whitespace Trim character with Print Close Delimiter should match\n    templateText = new StringBuilder().append(\"-}}\");\n    whitespaceTrimMatcher = pattern.matcher(templateText);\n    assertThat(whitespaceTrimMatcher.lookingAt()).isEqualTo(true);\n    \n    // leading space characters with Whitespace Trim character with Execution Close Delimiter should match\n    templateText = new StringBuilder().append(\"     -%}\");\n    whitespaceTrimMatcher = pattern.matcher(templateText);\n    assertThat(whitespaceTrimMatcher.lookingAt()).isEqualTo(true);\n    \n    // Whitespace Trim character with Comment Close Delimiter should match\n    templateText = new StringBuilder().append(\"-#}\");\n    whitespaceTrimMatcher = pattern.matcher(templateText);\n    assertThat(whitespaceTrimMatcher.lookingAt()).isEqualTo(true); \n    \n    // End of expression without Whitespace Trim character should not match\n    templateText = new StringBuilder().append(\"%}\");\n    whitespaceTrimMatcher = pattern.matcher(templateText);\n    assertThat(whitespaceTrimMatcher.lookingAt()).isEqualTo(false);\n    \n    // Leading non space characters should not match\n    templateText = new StringBuilder().append(\"abcd   -%}\");\n    whitespaceTrimMatcher = pattern.matcher(templateText);\n    assertThat(whitespaceTrimMatcher.lookingAt()).isEqualTo(false); \n  }\n \n  @Test\n  void testLeadingWhitespaceTrimRegex() {\n\t    \n\tPattern pattern = syntax.getRegexLeadingWhitespaceTrim();\n\n\t/*\n\t \"-\" followed by one more whitespace characters\n\t */\n    String expectedPatternString = \"\\\\Q-\\\\E\\\\s+\";\n    // verify that the regex in the Syntax class is the expected pattern string\n    assertThat(pattern.toString()).isEqualTo(expectedPatternString);\n    \n    StringBuilder templateText = null;\n    Matcher matcher = null;\n    \n    // Dash followed by spaces should match\n    templateText = new StringBuilder().append(\"- \t\");\n    matcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(true);\n    \n    // Dash character followed by whitespace characters should match\n    templateText = new StringBuilder().append(\"- \\n\\r\\t\");\n    matcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(true);\n    \n    // Trailing non-whitespace after whitespace characters should match\n    templateText = new StringBuilder().append(\"- abcd\");\n    matcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(true);\n    \n    // No leading dash character should not match\n    templateText = new StringBuilder().append(\"     \");\n    matcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(false);\n    \n    // No trailing space character should not match\n    templateText = new StringBuilder().append(\"-\");\n    matcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(false);\n    \n    // Leading characters should not match\n    templateText = new StringBuilder().append(\" abcd - \t}\");\n    matcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(false);\n  }\n  \n  @Test\n  void testRegexVerbatimEnd() {\n\t    \n\tPattern pattern = this.syntax.getRegexVerbatimEnd();\n\n\t/*  \n\t  \"{%\" followed by an optional \"-\" and/or zero or more whitespace characters, followed by \"endverbatim\",\n\t  followed by zero or more whitespace characters and/or an optional \"-\" followed by \"%}\" \n\t  followed by an optional possible new line character\n\t */ \n\tString expectedPatternString = \"\\\\Q{%\\\\E(\\\\Q-\\\\E)?\\\\s*endverbatim\\\\s*(\\\\Q-\\\\E)?\\\\Q%}\\\\E\" + POSSIBLE_NEW_LINE;\n    // verify that the regex in the Syntax class is the expected pattern string\n    assertThat(pattern.toString()).isEqualTo(expectedPatternString);\t  \n    \n    StringBuilder templateText = null;\n    Matcher matcher = null;\n    \n    // Space and whitespacetrim characters should match\n    templateText = new StringBuilder().append(\"{%- endverbatim -%}abcd\\r\\n\");\n\tmatcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(true);\n    \n    // No spaces or whitespacetrim characters should match\n    templateText = new StringBuilder().append(\"{%endverbatim%}\");\n\tmatcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(true);\n    \n    // Missing delimiters should not match\n    templateText = new StringBuilder().append(\"endverbatim\");\n\tmatcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(false);\n  }\n\n  @Test\n  void testRegexStartDelimiters() {\n\t  \n\tPattern pattern = this.syntax.getRegexStartDelimiters();\n\t  \n\t/*\n\t Start delimiters must match \"{{\" or \"{%\" or \"{#\"\n\t */\n\tString expectedPatternString = \"\\\\Q{{\\\\E|\\\\Q{%\\\\E|\\\\Q{#\\\\E\"; \n\t\n    // verify that the regex in the Syntax class is the expected pattern string\n    assertThat(pattern.toString()).isEqualTo(expectedPatternString);\t  \n    \n    StringBuilder templateText = null;\n    Matcher matcher = null;\n    \n    // \"{{\", \"{%\", and \"{#\" should match\n    templateText = new StringBuilder().append(\"{{\");\n\tmatcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(true);  \n    \n    templateText = new StringBuilder().append(\"{%\");\n  \tmatcher = pattern.matcher(templateText);\n      assertThat(matcher.lookingAt()).isEqualTo(true);\n      \n    templateText = new StringBuilder().append(\"{#\");\n  \tmatcher = pattern.matcher(templateText);\n      assertThat(matcher.lookingAt()).isEqualTo(true);\n      \n    // Text after a start delimiter should match\n    templateText = new StringBuilder().append(\"{{  abcd\");\n\tmatcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(true);\n    \n    // Text before the start delimiter should not match\n    templateText = new StringBuilder().append(\"abcd  {{\");\n\tmatcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(false);\n    \n    // No start delimiter should not match\n    templateText = new StringBuilder().append(\"abcd\");\n\tmatcher = pattern.matcher(templateText);\n    assertThat(matcher.lookingAt()).isEqualTo(false);\n  }\n  \n}\n\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/macro/MacroGlobalVariablesTest.java",
    "content": "package io.pebbletemplates.pebble.macro;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class MacroGlobalVariablesTest {\n\n  public static final class Extension extends AbstractExtension {\n    @Override\n    public Map<String, Object> getGlobalVariables() {\n      HashMap<String, Object> map = new HashMap<>();\n      map.put(\"someGlobalValue\", 18181);\n      return map;\n    }\n  }\n\n  @Test\n  void test() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).extension(new Extension()).build();\n    StringWriter writer = new StringWriter();\n    pebble.getTemplate(\"{% macro m() %}{{ someGlobalValue }}{% endmacro %}{{ m() }}\").evaluate(writer);\n    assertEquals(\"18181\", writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/macro/PebbleExtension.java",
    "content": "package io.pebbletemplates.pebble.macro;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Filter;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class PebbleExtension extends AbstractExtension {\n\n  @Override\n  public Map<String, Filter> getFilters() {\n    Map<String, Filter> f = new HashMap<>();\n    f.put(TestFilter.FILTER_NAME, new TestFilter());\n    return f;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/macro/TestFilter.java",
    "content": "package io.pebbletemplates.pebble.macro;\n\nimport io.pebbletemplates.pebble.extension.Filter;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\npublic class TestFilter implements Filter {\n\n  static int counter = 0;\n\n  public static final String FILTER_NAME = \"testfilter\";\n\n  @Override\n  public List<String> getArgumentNames() {\n    return Collections.singletonList(\"content\");\n  }\n\n  @Override\n  public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,\n                      EvaluationContext context, int lineNumber) {\n    String content = (String) input;\n    counter++;\n    content = content + \"?\" + \"Hello\";\n    return content;\n  }\n\n  public static int getCounter() {\n    return counter;\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/macro/TestMacroCalls.java",
    "content": "package io.pebbletemplates.pebble.macro;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n/**\n * Test calls of macros\n */\nclass TestMacroCalls {\n\n\n  /**\n   * Checks, if macros are called to often\n   */\n  @Test\n  void testMultipleMacroCalls() throws PebbleException, IOException {\n    // Build a pebble engine with one configured filter (\"testfilter\" - TestFilter.java)\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .extension(new PebbleExtension())\n        .strictVariables(false).build();\n    // Resets the counter of the filter\n    TestFilter.counter = 0;\n    /*\n     * Runs the test scenario:\n     * \tindex-template with an import of the macro and a call to this macro (Call #1)\n     * \tindex-templates includes \"include.peb\"\n     *\n     *  include.peb with an import of the macro and a call to this macro (Call #2)\n     *\n     *  We track the number of macro-calls by using a small \"Filter\" (TestFilter) that just\n     *  counts, how often it is called.\n     */\n    PebbleTemplate template = pebble.getTemplate(\"templates/macros/index.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n\n    // We expect, that the TestFilter was called 2x\n    assertEquals(2, TestFilter.getCounter());\n  }\n\n  @Test\n  void testMacroCallsWithImportAs() throws PebbleException, IOException {\n    // Build a pebble engine with one configured filter (\"testfilter\" - TestFilter.java)\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .extension(new PebbleExtension())\n        .strictVariables(false).build();\n    // Resets the counter of the filter\n    TestFilter.counter = 0;\n    /*\n     * Runs the test scenario:\n     *  import.as-template with an import-as of the macro and a call to this macro (Call #1)\n     *  import.as-templates includes \"include.peb\"\n     *\n     *  include.peb with an import of the macro and a call to this macro (Call #2)\n     *\n     *  We track the number of macro-calls by using a small \"Filter\" (TestFilter) that just\n     *  counts, how often it is called.\n     */\n    PebbleTemplate template = pebble.getTemplate(\"templates/macros/import.as.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n\n    // We expect, that the TestFilter was called 2x\n    assertEquals(2, TestFilter.getCounter());\n  }\n\n  @Test\n  void testMacroCallsWithFromToken() throws PebbleException, IOException {\n    // Build a pebble engine with one configured filter (\"testfilter\" - TestFilter.java)\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .extension(new PebbleExtension())\n        .strictVariables(false).build();\n    // Resets the counter of the filter\n    TestFilter.counter = 0;\n    /*\n     * Runs the test scenario:\n     *  from-template with an from-import-as of these macro and a call to these macro (Call #1, Call #3)\n     *  from-templates includes \"include.peb\"\n     *\n     *  include.peb with an import of the macro and a call to this macro (Call #2)\n     *\n     *  We track the number of macro-calls by using a small \"Filter\" (TestFilter) that just\n     *  counts, how often it is called.\n     */\n    PebbleTemplate template = pebble.getTemplate(\"templates/macros/from.peb\");\n    Writer writer = new StringWriter();\n    template.evaluate(writer);\n\n    // We expect, that the TestFilter was called 3x\n    assertEquals(3, TestFilter.getCounter());\n  }\n\n  @Test\n  void testInvalidMacroWithFromToken() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().build();\n\n    try {\n      PebbleTemplate template = pebble.getTemplate(\"templates/macros/invalid.from.peb\");\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      fail(\"expected PebbleException\");\n    } catch (PebbleException e) {\n      assertEquals(e.getLineNumber(), (Integer) 3);\n      assertEquals(e.getFileName(), \"templates/macros/invalid.from.peb\");\n    }\n  }\n\n  @Test\n  void testInvalidMacro() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().build();\n\n    try {\n      PebbleTemplate template = pebble.getTemplate(\"templates/macros/invalid.macro.peb\");\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      fail(\"expected PebbleException\");\n    } catch (PebbleException e) {\n      assertEquals(e.getLineNumber(), (Integer) 2);\n      assertEquals(e.getFileName(), \"templates/macros/invalid.macro.peb\");\n    }\n  }\n\n  @Test\n  void testInvalidSameAliasMacroWithFromToken() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().build();\n\n    try {\n      PebbleTemplate template = pebble.getTemplate(\"templates/macros/invalid.from.sameAlias.peb\");\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      fail(\"expected PebbleException\");\n    } catch (PebbleException e) {\n      assertTrue(\n          e.getPebbleMessage().startsWith(\"More than one macro can not share the same name\"));\n    }\n  }\n\n  @Test\n  void testInvalidSameAliasMacroWithImportAsToken() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().build();\n\n    try {\n      PebbleTemplate template = pebble\n          .getTemplate(\"templates/macros/invalid.import.as.sameAlias.peb\");\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      fail(\"expected PebbleException\");\n    } catch (PebbleException e) {\n      assertTrue(e.getPebbleMessage()\n          .startsWith(\"More than one named template can not share the same name\"));\n    }\n  }\n\n  @Test\n  void testInvalidAliasReferencingUnknownMacro() throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder().build();\n\n    try {\n      PebbleTemplate template = pebble\n          .getTemplate(\"templates/macros/invalid.from.unknownMacro.peb\");\n      Writer writer = new StringWriter();\n      template.evaluate(writer);\n      fail(\"expected PebbleException\");\n    } catch (PebbleException e) {\n      assertEquals(\n              \"Function or Macro [iDontExist] referenced by alias [macro_test] does not exist.\",\n              e.getPebbleMessage()\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/node/ForNodeTest.java",
    "content": "package io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass ForNodeTest {\n\n  @Test\n  void testVariableScope() throws Exception {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    StringBuilder source = new StringBuilder(\"{% set fooList = range(1, 1) %}\");\n    source.append(\"{% for item in fooList %}\");\n    source.append(\"{% set foo1 = 'fooValue' %}\");\n    source.append(\"Foo1 value : {{ foo1 }}\");\n    source.append(\"{% endfor %}\");\n    source.append(\"Foo1 value : {{ foo1 }}\");\n\n    source.append(\"{% for item in fooList %}\");\n    source.append(\"{% set foo2 = 'fooValue2' %}\");\n    source.append(\"Foo2 value : {{ foo2 }}\");\n    source.append(\"{% endfor %}\");\n    source.append(\"Foo2 value : {{ foo2 }}\");\n\n    PebbleTemplate template = pebble.getTemplate(source.toString());\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"Foo1 value : fooValueFoo1 value : Foo2 value : fooValue2Foo2 value : \",\n        writer.toString());\n  }\n\n  @Test\n  void testNestedLoop() throws Exception {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    StringBuilder source = new StringBuilder(\"{% for i in 0..2 %}\");\n    source.append(\"{% for j in 0..2 %}\");\n    source.append(\"i={{ i }} j={{ j }} \");\n    source.append(\"{% endfor %}\");\n    source.append(\"{% endfor %}\");\n    source.append(\"i={{ i }} j={{ j }} \");\n\n    PebbleTemplate template = pebble.getTemplate(source.toString());\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"i=0 j=0 i=0 j=1 i=0 j=2 i=1 j=0 i=1 j=1 i=1 j=2 i=2 j=0 i=2 j=1 i=2 j=2 i= j= \",\n        writer.toString());\n  }\n\n  @Test\n  void testLoopIndex() throws Exception {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    StringBuilder source = new StringBuilder(\"{% for i in 0..2 %}\");\n    source.append(\"{% for j in 0..2 %}\");\n    source.append(\"inner={{ loop.index }} \");\n    source.append(\"{% endfor %}\");\n    source.append(\"outer={{ loop.index }} \");\n    source.append(\"{% endfor %}\");\n    source.append(\"outside loop={{ loop.index }} \");\n\n    PebbleTemplate template = pebble.getTemplate(source.toString());\n\n    Map<String, Object> context = new HashMap<>();\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\n        \"inner=0 inner=1 inner=2 outer=0 inner=0 inner=1 inner=2 outer=1 inner=0 inner=1 inner=2 outer=2 outside loop= \",\n        writer.toString());\n  }\n\n  @Test\n  void loopOverEnumeration() throws Exception {\n    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n        .strictVariables(false).build();\n\n    StringBuilder source = new StringBuilder(\"{% for item in fooEnumeration %}\");\n    source.append(\"{{ item }}\");\n    source.append(\"{% endfor %}\");\n\n    PebbleTemplate template = pebble.getTemplate(source.toString());\n\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"fooEnumeration\", new Enumeration<String>() {\n\n      private int value = 0;\n\n      @Override\n      public boolean hasMoreElements() {\n        return this.value < 10;\n      }\n\n      @Override\n      public String nextElement() {\n        return String.valueOf(this.value++);\n      }\n    });\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    assertEquals(\"0123456789\", writer.toString());\n  }\n\n}"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/node/IfNodeTest.java",
    "content": "package io.pebbletemplates.pebble.node;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass IfNodeTest {\n\n  private static final String templateSource = \"{% if value %}yes{% else %}no{% endif %}\";\n\n  private String render(Object foobar) throws IOException {\n    return this.render(false, foobar);\n  }\n\n  private String render(boolean strict, Object value) throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n            .strictVariables(strict)\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(templateSource);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"value\", value);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    return writer.toString();\n  }\n\n\n  @Test\n  void testIfNull() throws IOException {\n    assertEquals(\"no\", this.render(null), \"Null should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNullInStrictMode() throws IOException {\n    assertThrows(PebbleException.class, () -> this.render(true, null));\n  }\n\n  @Test\n  void testIfTrue() throws IOException {\n    assertEquals(\"yes\", this.render(true), \"Null should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfFalse() throws IOException {\n    assertEquals(\"no\", this.render(false), \"Null should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNotZeroInteger() throws IOException {\n    assertEquals(\"yes\", this.render(1), \"Not zero integer should be interpreted as TRUE\");\n  }\n\n  @Test\n  void testIfZeroInteger() throws IOException {\n    assertEquals(\"no\", this.render(0), \"Zero integer should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNotZeroFloat() throws IOException {\n    assertEquals(\"yes\", this.render(1.1), \"Not zero float should be interpreted as TRUE\");\n  }\n\n  @Test\n  void testIfZeroFloat() throws IOException {\n    assertEquals(\"no\", this.render(0), \"Zero float should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfZeroDoubleNaN() throws IOException {\n    assertEquals(\"no\", this.render(Double.NaN), \"NaN should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfZeroFloatNaN() throws IOException {\n    assertEquals(\"no\", this.render(Float.NaN), \"NaN should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNotEmptyString() throws IOException {\n    assertEquals(\"yes\", this.render(\"not empty string\"), \"Not empty string should be interpreted as TRUE\");\n  }\n\n  @Test\n  void testIfEmptyString() throws IOException {\n    assertEquals(\"no\", this.render(\"\"), \"Empty string should be interpreted as FALSE\");\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/node/expression/AndExpressionTest.java",
    "content": "package io.pebbletemplates.pebble.node.expression;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\n\n\nclass AndExpressionTest extends ExpressionTest {\n\n  @Test\n  void testIntExpressionPartFalse() throws IOException {\n    this.testExpression(\"{{ 0 and true }}\", \"false\");\n    this.testExpression(\"{{ false or 0 }}\", \"false\");\n  }\n\n  @Test\n  void testIntExpressionPartTrue() throws IOException {\n    this.testExpression(\"{{ 1 and false }}\", \"false\");\n    this.testExpression(\"{{ true or 1 }}\", \"true\");\n  }\n\n  @Test\n  void testStringExpressionPartFalse() throws IOException {\n    this.testExpression(\"{{ '' and true }}\", \"false\");\n    this.testExpression(\"{{ true and '' }}\", \"false\");\n  }\n\n  @Test\n  void testStringExpressionPartTrue() throws IOException {\n    this.testExpression(\"{{ 'true' and false }}\", \"false\");\n    this.testExpression(\"{{ true or 'true' }}\", \"true\");\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/node/expression/ExpressionTest.java",
    "content": "package io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic abstract class ExpressionTest {\n\n  private PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader())\n      .strictVariables(false).build();\n\n  protected void testExpression(String templateName, String expected) throws IOException {\n    PebbleTemplate template = this.pebble.getTemplate(templateName);\n    StringWriter writer = new StringWriter();\n    template.evaluate(writer);\n    assertEquals(expected, writer.toString());\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/node/expression/OrExpressionTest.java",
    "content": "package io.pebbletemplates.pebble.node.expression;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\n\nclass OrExpressionTest extends ExpressionTest {\n\n  @Test\n  void testIntExpressionPartFalse() throws IOException {\n    this.testExpression(\"{{ 0 or false }}\", \"false\");\n    this.testExpression(\"{{ true or 0 }}\", \"true\");\n  }\n\n  @Test\n  void testIntExpressionPartTrue() throws IOException {\n    this.testExpression(\"{{ 1 or false }}\", \"true\");\n    this.testExpression(\"{{ true or 1 }}\", \"true\");\n  }\n\n  @Test\n  void testStringExpressionPartFalse() throws IOException {\n    this.testExpression(\"{{ '' or false }}\", \"false\");\n    this.testExpression(\"{{ true or '' }}\", \"true\");\n  }\n\n  @Test\n  void testStringExpressionPartTrue() throws IOException {\n    this.testExpression(\"{{ 'true' or false }}\", \"true\");\n    this.testExpression(\"{{ true or 'true' }}\", \"true\");\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/node/expression/StringExpressionParserTest.java",
    "content": "package io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.ParserException;\nimport io.pebbletemplates.pebble.lexer.LexerImpl;\nimport io.pebbletemplates.pebble.lexer.TokenStream;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.parser.Parser;\nimport io.pebbletemplates.pebble.parser.ParserImpl;\nimport io.pebbletemplates.pebble.parser.ParserOptions;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.node.PrintNode;\nimport io.pebbletemplates.pebble.node.RootNode;\nimport io.pebbletemplates.pebble.utils.Pair;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringReader;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass StringExpressionParserTest {\n\n  private Pair<Parser, RootNode> testParseExpression(String expression) {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n        .strictVariables(false)\n        .build();\n\n    LexerImpl lexer = new LexerImpl(\n        pebble.getSyntax(),\n        pebble.getExtensionRegistry().getUnaryOperators().values(),\n        pebble.getExtensionRegistry().getBinaryOperators().values()\n    );\n\n    TokenStream tokenStream = lexer.tokenize(new StringReader(expression), \"test.peb\");\n\n    Parser parser = new ParserImpl(\n        pebble.getExtensionRegistry().getUnaryOperators(),\n        pebble.getExtensionRegistry().getBinaryOperators(),\n        pebble.getExtensionRegistry().getTokenParsers(),\n        new ParserOptions()\n    );\n\n    return new Pair<>(parser, parser.parse(tokenStream));\n  }\n\n  /*\n   * Sequential String literals are not allowed, a syntax error should be thrown\n   */\n  @Test\n  void testSequentialStrings_singleQuotes_isSyntaxError() throws IOException {\n    assertThrows(ParserException.class, () -> this.testParseExpression(\"{{ 'one' 'two' }}\"));\n  }\n\n  /*\n   * Sequential String literals are not allowed, a syntax error should be thrown\n   */\n  @Test\n  void testSequentialStrings_doubleQuotes_isSyntaxError() throws IOException {\n    assertThrows(ParserException.class, () -> this.testParseExpression(\"{{ \\\"one\\\" \\\"two\\\" }}\"));\n  }\n\n  /*\n   * + parses as Add expression. AST should be:\n   *\n   * AddExpression\n   *      LiteralStringExpression('one')\n   *      LiteralStringExpression('two')\n   */\n  @Test\n  void testValidExpression_singleQuotes() throws IOException {\n    Pair<Parser, RootNode> underTest = this.testParseExpression(\"{{ 'one' + 'two' }}\");\n\n    PrintNode printNode = (PrintNode) underTest.getRight().getBody().getChildren().get(0);\n    Expression<?> expr = printNode.getExpression();\n\n    assertThat(expr).isInstanceOf(AddExpression.class);\n\n    Expression<?> leftExpression = ((AddExpression) expr).getLeftExpression();\n    Expression<?> rightExpression = ((AddExpression) expr).getRightExpression();\n\n    assertThat(leftExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) leftExpression).getValue()).isEqualTo(\"one\");\n\n    assertThat(rightExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) rightExpression).getValue()).isEqualTo(\"two\");\n  }\n\n  /*\n   * + parses as Add expression. AST should be:\n   *\n   * AddExpression\n   *      LiteralStringExpression('one')\n   *      LiteralStringExpression('two')\n   */\n  @Test\n  void testValidExpression_doubleQuotes() throws IOException {\n    Pair<Parser, RootNode> underTest = this.testParseExpression(\"{{ \\\"one\\\" + \\\"two\\\" }}\");\n\n    PrintNode printNode = (PrintNode) underTest.getRight().getBody().getChildren().get(0);\n    Expression<?> expr = printNode.getExpression();\n\n    assertThat(expr).isInstanceOf(AddExpression.class);\n\n    Expression<?> leftExpression = ((AddExpression) expr).getLeftExpression();\n    Expression<?> rightExpression = ((AddExpression) expr).getRightExpression();\n\n    assertThat(leftExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) leftExpression).getValue()).isEqualTo(\"one\");\n\n    assertThat(rightExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) rightExpression).getValue()).isEqualTo(\"two\");\n  }\n\n  /*\n   * ~ parses as Concatenate expression. AST should be:\n   *\n   * ConcatenateExpression\n   *      LiteralStringExpression('one')\n   *      LiteralStringExpression('two')\n   */\n  @Test\n  void testValidConcatenationExpression_singleQuotes() throws IOException {\n    Pair<Parser, RootNode> underTest = this.testParseExpression(\"{{ 'one' ~ 'two' }}\");\n\n    PrintNode printNode = (PrintNode) underTest.getRight().getBody().getChildren().get(0);\n    Expression<?> expr = printNode.getExpression();\n\n    assertThat(expr).isInstanceOf(ConcatenateExpression.class);\n\n    Expression<?> leftExpression = ((ConcatenateExpression) expr).getLeftExpression();\n    Expression<?> rightExpression = ((ConcatenateExpression) expr).getRightExpression();\n\n    assertThat(leftExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) leftExpression).getValue()).isEqualTo(\"one\");\n\n    assertThat(rightExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) rightExpression).getValue()).isEqualTo(\"two\");\n  }\n\n  /*\n   * ~ parses as Concatenate expression. AST should be:\n   *\n   * ConcatenateExpression\n   *      LiteralStringExpression('one')\n   *      LiteralStringExpression('two')\n   */\n  @Test\n  void testValidConcatenationExpression_doubleQuotes() throws IOException {\n    Pair<Parser, RootNode> underTest = this.testParseExpression(\"{{ \\\"one\\\" ~ \\\"two\\\" }}\");\n\n    PrintNode printNode = (PrintNode) underTest.getRight().getBody().getChildren().get(0);\n    Expression<?> expr = printNode.getExpression();\n\n    assertThat(expr).isInstanceOf(ConcatenateExpression.class);\n\n    Expression<?> leftExpression = ((ConcatenateExpression) expr).getLeftExpression();\n    Expression<?> rightExpression = ((ConcatenateExpression) expr).getRightExpression();\n\n    assertThat(leftExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) leftExpression).getValue()).isEqualTo(\"one\");\n\n    assertThat(rightExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) rightExpression).getValue()).isEqualTo(\"two\");\n  }\n\n  /*\n   * Single-quotes does not parse interpolations. AST should be:\n   *\n   * LiteralStringExpression('one #{two} three')\n   */\n  @Test\n  void testValidInterpolationExpression_singleQuotes() throws IOException {\n    Pair<Parser, RootNode> underTest = this.testParseExpression(\"{{ 'one #{two} three' }}\");\n\n    PrintNode printNode = (PrintNode) underTest.getRight().getBody().getChildren().get(0);\n    Expression<?> expr = printNode.getExpression();\n\n    assertThat(expr).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) expr).getValue()).isEqualTo(\"one #{two} three\");\n  }\n\n  /*\n   * Double-quotes parses interpolations. AST should be:\n   *\n   * ConcatenateExpression\n   *      LiteralStringExpression('one ')\n   *      ConcatenateExpression\n   *          ContextVariableExpression(two)\n   *          LiteralStringExpression(' three')\n   */\n  @Test\n  void testValidInterpolationExpression_doubleQuotes() throws IOException {\n    Pair<Parser, RootNode> underTest = this.testParseExpression(\"{{ \\\"one #{two} three\\\" }}\");\n\n    PrintNode printNode = (PrintNode) underTest.getRight().getBody().getChildren().get(0);\n    Expression<?> expr = printNode.getExpression();\n\n    assertThat(expr).isInstanceOf(ConcatenateExpression.class);\n\n    Expression<?> leftExpression = ((ConcatenateExpression) expr).getLeftExpression();\n    Expression<?> rightExpression = ((ConcatenateExpression) expr).getRightExpression();\n\n    assertThat(leftExpression).isInstanceOf(LiteralStringExpression.class);\n    assertThat(((LiteralStringExpression) leftExpression).getValue()).isEqualTo(\"one \");\n\n    assertThat(rightExpression).isInstanceOf(ConcatenateExpression.class);\n\n    Expression<?> right_leftSubExpression = ((ConcatenateExpression) rightExpression).getLeftExpression();\n    assertThat(((ContextVariableExpression) right_leftSubExpression).getName()).isEqualTo(\"two\");\n\n    Expression<?> right_rightSubExpression = ((ConcatenateExpression) rightExpression).getRightExpression();\n        assertThat(((LiteralStringExpression) right_rightSubExpression).getValue()).isEqualTo(\" three\");\n    }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/node/expression/UnaryNotExpressionTest.java",
    "content": "package io.pebbletemplates.pebble.node.expression;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass UnaryNotExpressionTest {\n\n  private static final String templateSource = \"{% if not value %}yes{% else %}no{% endif %}\";\n\n  private String render(Object foobar) throws IOException {\n    return this.render(false, foobar);\n  }\n\n  private String render(boolean strict, Object value) throws IOException {\n    PebbleEngine pebble = new PebbleEngine.Builder()\n        .loader(new StringLoader())\n            .strictVariables(strict)\n            .build();\n\n    PebbleTemplate template = pebble.getTemplate(templateSource);\n    Map<String, Object> context = new HashMap<>();\n    context.put(\"value\", value);\n\n    Writer writer = new StringWriter();\n    template.evaluate(writer, context);\n    return writer.toString();\n  }\n\n  @Test\n  void testIfNotNull() throws IOException {\n    assertEquals(\"yes\", this.render(null), \"Not Null should be interpreted as TRUE\");\n  }\n\n  @Test\n  void testIfNullInStrictMode() throws IOException {\n    assertThrows(PebbleException.class, () -> this.render(true, null));\n  }\n\n  @Test\n  void testIfNotTrue() throws IOException {\n    assertEquals(\"no\", this.render(true), \"Not true should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNotFalse() throws IOException {\n    assertEquals(\"yes\", this.render(false), \"Not false should be interpreted as TRUE\");\n  }\n\n  @Test\n  void testIfNotIntegerDifferentThanZero() throws IOException {\n    assertEquals(\"no\", this.render(1), \"Not Integer one should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNotIntegerZero() throws IOException {\n    assertEquals(\"yes\", this.render(0), \"Not Integer Zero should be interpreted as TRUE\");\n  }\n\n  @Test\n  void testIfNotFloatDifferentThanZero() throws IOException {\n    assertEquals(\"no\", this.render(1.1), \"Not float different than zero should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNotFloatZero() throws IOException {\n    assertEquals(\"yes\", this.render(0.0), \"Not float zero should be interpreted as TRUE\");\n  }\n\n  @Test\n  void testIfNotString() throws IOException {\n    assertEquals(\"no\", this.render(\"not empty string\"), \"Not string should be interpreted as FALSE\");\n  }\n\n  @Test\n  void testIfNotEmptyString() throws IOException {\n    assertEquals(\"yes\", this.render(\"\"), \"Not Empty string should be interpreted as TRUE\");\n  }\n\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/template/tests/PebbleTestContext.java",
    "content": "package io.pebbletemplates.pebble.template.tests;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.StringLoader;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Used by Pebble Template Tests to simply the test code and therefore \n * make it easier to understand what is tested by each test.\n * \n *  A separate instance of this class should be instantiated for each\n *  template test, i.e. for each instance of a template. \n * \n * @author nathanward\n */\npublic class PebbleTestContext {\n\n\tprivate final Logger logger = LoggerFactory.getLogger(PebbleTestContext.class);\n\t\n\t/**\n\t * The path relative to the project root directory where template files and \n\t * expected output files are stored for test purposes.\n\t */\n\tprivate String testFileBasePath = \"template-tests\";\n\t\n\t/**\n\t * The Pebble template context to be used as input for a Pebble Template. \n\t */\n\tprivate Map<String, Object> templateContext = null;\n\n\t/**\n\t * Whether or not the Pebble Engine instantiated will \n\t * enable New Line Trimming on the Builder when this class instantiates\n\t * a Pebble Engine instance. Defaults to <code>true</code>.\n\t */\n\tprivate boolean newLineTrimming = true;\n\t\n\t/**\n\t * Initialize the Pebble Template Context.\n\t */ \n\tpublic PebbleTestContext() {\n\t\tthis.templateContext = new HashMap<String, Object>();\n\t}\n\t\n\tpublic void setNewLineTrimming(boolean b) {\n\t\tthis.newLineTrimming = b;\n\t}\n\t\n\t/**\n\t * Put an object into the Pebble template context to be used as input for\n\t * when the template is executed. One or more items can be put into the template\n\t * context.\n\t * \n\t * @param name Template input/parameter name (i.e. key) for use within the template\n\t * @param value The object that will be referred to by the given name in the template\n\t */\n\tpublic void setTemplateInput(String name, Object value) {\n\t\tthis.templateContext.put(name, value);\n\t}\n\n\t/**\n\t * Load the specified template file and execute the template using a \n\t * Pebble Engine using the default Builder (classpath and file builder). \n\t * \n\t * @param templateFilename The template filename relative to the Test File Base Path\n\t * \n\t * @return The output of the template as a string.\n\t * \n\t * @throws IOException Thrown if the template file is not found. \n\t */\n\tpublic String executeTemplateFromFile(String templateFilename) throws IOException {\n\t\tPebbleEngine pebbleEngine = new PebbleEngine.Builder()\n\t\t\t\t.newLineTrimming(this.newLineTrimming).strictVariables(true).build();\n\t\treturn this.executeTemplateFromFile(templateFilename, pebbleEngine);\n\t}\n\t\n\t/**\n\t * Execute a template given a template file and a Pebble Engine instance. \n\t * \n\t * @param templateFilename The template filename relative to the Test File Base Path\n\t * @param pebbleEngine\n\t * @return\n\t * @throws IOException\n\t */\n\tpublic String executeTemplateFromFile(String templateFilename, PebbleEngine pebbleEngine) throws IOException {\n\t\tlogger.debug(\"Executing template file: {}\", templateFilename);\n\t\treturn this.executeTemplate(testFileBasePath + \"/\" + templateFilename, pebbleEngine);\n\t}\n\t\t\n\t/**\n\t * Load the specified template file and execute the template using a \n\t * Pebble Engine using the default Builder (classpath and file builder). \n\t * \n\t * @param templateString The template content as a string\n\t * \n\t * @return The output of the template as a string.\n\t * \n\t * @throws IOException Thrown if the template file is not found. \n\t */\n\tpublic String executeTemplateFromString(String templateString) throws IOException {\n\t\tPebbleEngine pebbleEngine = new PebbleEngine.Builder().loader(new StringLoader())\n\t\t\t\t.newLineTrimming(this.newLineTrimming).build();\n\t\treturn this.executeTemplate(templateString, pebbleEngine);\n\t}\n\t\n\t/**\n\t * Load the specified template file and execute the template using the template input\n\t * that has previously been specified using the setTemplateInput() method. \n\t * \n\t * @param templateFilename The template filename relative to the Test File Base Path\n\t * @param pebbleEngine The Pebble Engine to be used to execute the template\n\t * @return The output of the template as a string.\n\t * \n\t * @throws IOException Thrown if the template file is not found. \n\t */\n\tpublic String executeTemplate(String templateName, PebbleEngine pebbleEngine) throws IOException {\n\t\tPebbleTemplate template = pebbleEngine.getTemplate(templateName);\n\t\tWriter writer = new StringWriter();\n\t\ttemplate.evaluate(writer, this.templateContext);\n\t\tString templateOutput = writer.toString();\n\t\tlogger.debug(\"Template Output:\\n{}\", templateOutput);\n\t\treturn templateOutput;\n\t}\n\t\n\t/**\n\t * Get the Expected Output content for the given filename so that the \n\t * base path to the expected template output file does not have to be\n\t * specified in the actual test code. \n\t * \n\t * @param filename The name of the file that contains the expected template output. \n\t * @return The content of the expected template output file as a string.\n\t * @throws IOException Thrown if the file by the given name is not found.\n\t */\n\tpublic String getExpectedOutput(String filename) throws IOException {\n    URL resource = this.getClass().getClassLoader().getResource(testFileBasePath + \"/\" + filename);\n    try {\n      return new String(Files.readAllBytes(Paths.get(resource.toURI())));\n    } catch (URISyntaxException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/template/tests/WhiteSpaceControlWithNewLineTrimmingTests.java",
    "content": "/*\n * This file is part of Pebble.\n *\n * Copyright (c) 2014 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.pebble.template.tests;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.tests.input.PebbleTestItem;\nimport io.pebbletemplates.pebble.template.tests.input.PebbleTestItemType;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests of whitespace control when New Line Trimming is enabled.\n * This mainly affects vertical spacing, i.e. the control of blank lines.\n * With New Line Trimming enabled, Pebble control statements on a line\n * by themselves products a blank line in the output unless\n * the Whitespace Control Modifier is used before and/or after the\n * control statement, i.e. the dash character, e.g.  `{% if item.itemType equals \"ITEM_TYPE1\" -%}`.\n *\n * However, it can be difficult to get the blank lines to come out as expected\n * in the case that nested control structures are used.\n *\n * @author nathanward\n */\nclass WhiteSpaceControlWithNewLineTrimmingTests {\n\n  /**\n   * All tests in this class use this list of objects as the input to the template.\n   */\n  private List<PebbleTestItem> listOfObjects = null;\n\n  /**\n   * Used by each test and initialized before each test to simplify the\n   * test execution, which helps make the purpose of each test clear.\n   */\n  private PebbleTestContext pebbleTestContext = null;\n\n  @BeforeEach\n  void setup() {\n    this.pebbleTestContext = new PebbleTestContext();\n    this.pebbleTestContext.setNewLineTrimming(false);\n\n    this.listOfObjects = new ArrayList<PebbleTestItem>();\n    this.listOfObjects.add(new PebbleTestItem(\"Item 1\", PebbleTestItemType.ITEM_TYPE1));\n    this.listOfObjects.add(new PebbleTestItem(\"Item 2\", PebbleTestItemType.ITEM_TYPE2));\n    this.listOfObjects.add(new PebbleTestItem(\"Item 3\", PebbleTestItemType.ITEM_TYPE3));\n    this.listOfObjects.add(new PebbleTestItem(\"Item 4\", PebbleTestItemType.ITEM_TYPE4, true));\n\n    this.pebbleTestContext.setTemplateInput(\"items\", this.listOfObjects);\n  }\n\n  /**\n   * Test the whitespace control for a template that has a <code>for</code> loop with a nested\n   * <code>if</code> statement that uses a macro and the macro also has an if statement.\n   */\n  @Test\n  void testForLoopWithNestedIfStatementAndMacro() throws PebbleException, IOException {\n    String templateOutput = this.pebbleTestContext.executeTemplateFromFile(\"ForLoopWithNestedIfStatementAndMacro.peb\");\n    assertThat(templateOutput).contains(this.pebbleTestContext.getExpectedOutput(\"ForLoopWithNestedIfStatementAndMacro.txt\"));\n  }\n\n  /**\n   * Test the whitespace control for a template that has a <code>for</code> loop with a nested\n   * <code>if</code> statement that uses a macro and the macro also has an if statement.\n   */\n  // TODO not sure why the white space controls work the way that it does for this case.\n  @Test\n  void testDoubleNestedIfStatement() throws PebbleException, IOException {\n    String templateOutput = this.pebbleTestContext.executeTemplateFromFile(\"DoubleNestedIfStatement.peb\");\n    assertThat(templateOutput).contains(this.pebbleTestContext.getExpectedOutput(\"DoubleNestedIfStatement.txt\"));\n  }\n\n  /**\n   * Test the whitespace control for a template that has a <code>for</code> loop with a nested\n   * <code>if</code> statement where some text is output for each item in the list.\n   */\n  @Test\n  void testNestedIfStatementWithOneElseIfStatements() throws PebbleException, IOException {\n    String templateOutput = this.pebbleTestContext.executeTemplateFromFile(\"NestedIfStatementWithOneElseIfStatements.peb\");\n    assertThat(templateOutput).contains(this.pebbleTestContext.getExpectedOutput(\"NestedIfStatementWithOneElseIfStatements.txt\"));\n  }\n\n  /**\n   * Test the whitespace control for a template that has a <code>for</code> loop with a nested\n   * <code>if</code> statement where some text is output for each item in the list.\n   */\n  @Test\n  void testNestedIfStatementWithTwoElseIfStatements() throws PebbleException, IOException {\n    String templateOutput = this.pebbleTestContext.executeTemplateFromFile(\"NestedIfStatementWithTwoElseIfStatements.peb\");\n    assertThat(templateOutput).contains(this.pebbleTestContext.getExpectedOutput(\"NestedIfStatementWithTwoElseIfStatements.txt\"));\n  }\n\n  /**\n   * Test the whitespace control for a template that has a <code>for</code> loop with a nested\n   * <code>if</code> statement that skips some of the items in the for loop.\n   */\n  @Test\n  void testNestedIfStatementWithThreeElseIfStatements() throws IOException {\n    String templateOutput = this.pebbleTestContext.executeTemplateFromFile(\"NestedIfStatementWithThreeElseIfStatements.peb\");\n    assertThat(templateOutput).contains(this.pebbleTestContext.getExpectedOutput(\"NestedIfStatementWithThreeElseIfStatements.txt\"));\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/template/tests/input/PebbleTestItem.java",
    "content": "package io.pebbletemplates.pebble.template.tests.input;\n\npublic class PebbleTestItem {\n\n\tprivate String name;\n\t\n\tprivate PebbleTestItemType itemType;\n\t\n\tprivate boolean hasPrefix;\n\n\tpublic PebbleTestItem(String name, PebbleTestItemType itemType1) {\n\t\tthis.name = name;\n\t\tthis.itemType = itemType1;\n\t}\n\n\tpublic PebbleTestItem(String name, PebbleTestItemType itemType1, boolean hasPrefix) {\n\t\tthis.name = name;\n\t\tthis.itemType = itemType1;\n\t\tthis.hasPrefix = hasPrefix;\n\t}\n\t\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic PebbleTestItemType getItemType() {\n\t\treturn itemType;\n\t}\n\n\tpublic void setItemType(PebbleTestItemType pebbleTestItemType) {\n\t\tthis.itemType = pebbleTestItemType;\n\t}\n\n\tpublic boolean isHasPrefix() {\n\t\treturn hasPrefix;\n\t}\n\n\tpublic void setHasPrefix(boolean hasPrefix) {\n\t\tthis.hasPrefix = hasPrefix;\n\t}\n\t\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/template/tests/input/PebbleTestItemType.java",
    "content": "package io.pebbletemplates.pebble.template.tests.input;\n\npublic enum PebbleTestItemType {\n\tITEM_TYPE1, ITEM_TYPE2, ITEM_TYPE3, ITEM_TYPE4\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/utils/LimitedSizeWriterTest.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.RenderedSizeContext;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests {@link LimitedSizeWriter}.\n */\nclass LimitedSizeWriterTest {\n    @Test\n    void negativeMaxSizeReturnsTheOriginalWriter() {\n        Writer internalWriter = new StringWriter();\n\n        Writer limitedSizeWriter = LimitedSizeWriter.from(internalWriter, new TestRenderedSizeContext(-1));\n\n        assertSame(internalWriter, limitedSizeWriter);\n    }\n\n    @Test\n    void canWriteLessThanLimitChars() throws IOException {\n        Writer internalWriter = new StringWriter();\n\n        Writer limitedSizeWriter = LimitedSizeWriter.from(internalWriter, new TestRenderedSizeContext(20));\n\n        limitedSizeWriter.write(\"0123456789\");\n        limitedSizeWriter.write(\"0123456789\");\n\n        assertEquals(\"01234567890123456789\", internalWriter.toString());\n    }\n\n    @Test\n    void cannotWriteMoreThanLimitChars() throws IOException {\n        Writer internalWriter = new StringWriter();\n\n        Writer limitedSizeWriter = LimitedSizeWriter.from(internalWriter, new TestRenderedSizeContext(19));\n\n        limitedSizeWriter.write(\"0123456789\");\n\n        PebbleException thrown = assertThrows(\n                PebbleException.class,\n                () -> limitedSizeWriter.write(\"0123456789\"));\n\n        assertTrue(thrown.getMessage().contains(\"19\"));\n    }\n\n    @Test\n    void contextIsSharedBetweenWriters() throws IOException {\n        Writer internalWriter1 = new StringWriter();\n        Writer internalWriter2 = new StringWriter();\n\n        TestRenderedSizeContext context = new TestRenderedSizeContext(19);\n        Writer limitedSizeWriter1 = LimitedSizeWriter.from(internalWriter1, context);\n        Writer limitedSizeWriter2 = LimitedSizeWriter.from(internalWriter2, context);\n\n        limitedSizeWriter1.write(\"0123456789\");\n\n        PebbleException thrown = assertThrows(\n                PebbleException.class,\n                () -> limitedSizeWriter2.write(\"0123456789\"));\n\n        assertTrue(thrown.getMessage().contains(\"19\"));\n    }\n\n    // This is the exact same implementation as in EvaluationContextImpl.\n    static private class TestRenderedSizeContext implements RenderedSizeContext {\n        private final int maxRenderedSize;\n        private final AtomicInteger charsRendered = new AtomicInteger();\n\n        public TestRenderedSizeContext(int maxRenderedSize) {\n            this.maxRenderedSize = maxRenderedSize;\n        }\n\n        @Override\n        public int getMaxRenderedSize() {\n            return this.maxRenderedSize;\n        }\n\n        @Override\n        public int addAndGet(int delta) {\n            return this.charsRendered.addAndGet(delta);\n        }\n    }\n}\n"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/utils/OperatorUtilsToNumberTest.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.math.BigDecimal;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\nclass OperatorUtilsToNumberTest {\n\n    @ParameterizedTest(name = \"Number input {0} should be returned as-is\")\n    @MethodSource(\"numberInputs\")\n    void shouldReturnNumberInstanceAsIs(Number input) {\n        Number result = OperatorUtils.toNumber(input);\n        assertThat(result).isSameAs(input);\n    }\n\n    static Stream<Number> numberInputs() {\n        return Stream.of(42, 3.14f, 100L, 2.718281828, 2.5d, (short) 7, new BigDecimal(12345678900L));\n    }\n\n    @ParameterizedTest(name = \"String {0} should parse to Double {1}\")\n    @MethodSource(\"decimalStringInputs\")\n    void shouldParseDecimalStringAsDouble(String input, double expected) {\n        Number result = OperatorUtils.toNumber(input);\n        assertThat(result)\n            .isInstanceOf(Double.class)\n            .isEqualTo(expected);\n    }\n\n    static Stream<Arguments> decimalStringInputs() {\n        return Stream.of(\n            arguments(\"3.14\",   3.14),\n            arguments(\"0.0\",    0.0),\n            arguments(\"-1.5\",  -1.5),\n            arguments(\"1e10\",   1e10),\n            arguments(\"1E10\",   1E10),\n            arguments(\"2.5e3\",  2.5e3),\n            arguments(\"1.1E-2\", 1.1E-2)\n        );\n    }\n\n    @ParameterizedTest(name = \"String {0} should parse to Long {1}\")\n    @MethodSource(\"integerStringInputs\")\n    void shouldParseIntegerStringAsLong(String input, long expected) {\n        Number result = OperatorUtils.toNumber(input);\n        assertThat(result)\n            .isInstanceOf(Long.class)\n            .isEqualTo(expected);\n    }\n\n    static Stream<Arguments> integerStringInputs() {\n        return Stream.of(\n            arguments(\"0\",                    0L),\n            arguments(\"42\",                  42L),\n            arguments(\"-7\",                  -7L),\n            arguments(\"9999999999\",  9999999999L)  // larger than Integer.MAX_VALUE\n        );\n    }\n\n    @ParameterizedTest(name = \"Unsupported type {0} should throw IllegalArgumentException\")\n    @MethodSource(\"unsupportedInputs\")\n    void shouldThrowForUnsupportedTypes(Object input) {\n        assertThatThrownBy(() -> OperatorUtils.toNumber(input))\n            .isInstanceOf(IllegalArgumentException.class)\n            .hasMessageContaining(\"Cannot convert to Number\");\n    }\n\n    static Stream<Object> unsupportedInputs() {\n        return Stream.of(null, true, 'x', new Object());\n    }\n\n    @ParameterizedTest(name = \"Malformed string {0} should throw NumberFormatException\")\n    @ValueSource(strings = {\"abc\", \"1.2.3\", \"1e\", \"--1\", \"\"})\n    void shouldThrowForMalformedStrings(String input) {\n        assertThatThrownBy(() -> OperatorUtils.toNumber(input))\n            .isInstanceOf(NumberFormatException.class);\n    }\n}"
  },
  {
    "path": "pebble/src/test/java/io/pebbletemplates/pebble/utils/PathUtilsTest.java",
    "content": "package io.pebbletemplates.pebble.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\n/**\n * Tests {@link PathUtils}.\n *\n * @author Thomas Hunziker\n */\nclass PathUtilsTest {\n\n  private static final char FORWARD_SLASH = '/';\n  private static final char BACKWARD_SLASH = '\\\\';\n\n  /**\n   * Tests if {@link PathUtils#resolveRelativePath(String, String, char)} is working with Unix\n   * systems.\n   */\n  @Test\n  void testRelativePathResolutionUnixStyle() {\n    assertEquals(\"test/test/sample.peb\",\n        PathUtils.resolveRelativePath(\"./sample.peb\", \"test/test/\", FORWARD_SLASH));\n    assertEquals(\"test/test/sample.peb\",\n        PathUtils.resolveRelativePath(\"./sample.peb\", \"test/test/other.peb\", FORWARD_SLASH));\n    assertEquals(\"test/sample.peb\",\n        PathUtils.resolveRelativePath(\"../sample.peb\", \"test/test/\", FORWARD_SLASH));\n    assertEquals(\"test/sample.peb\",\n        PathUtils.resolveRelativePath(\"../sample.peb\", \"test/test/other.peb\", FORWARD_SLASH));\n    assertNull(\n        PathUtils.resolveRelativePath(\"test/sample.peb\", \"test/test/other.peb\", FORWARD_SLASH));\n\n  }\n\n  /**\n   * Tests if {@link PathUtils#resolveRelativePath(String, String, char)} is working with Windows.\n   */\n  @Test\n  void testRelativePathResolutionWindowsStyle() {\n    assertEquals(\"test\\\\test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\".\\\\sample.peb\", \"test\\\\test\\\\\", BACKWARD_SLASH));\n    assertEquals(\"test\\\\test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\".\\\\sample.peb\", \"test\\\\test\\\\other.peb\", BACKWARD_SLASH));\n    assertEquals(\"test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\"..\\\\sample.peb\", \"test\\\\test\\\\\", BACKWARD_SLASH));\n    assertEquals(\"test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\"..\\\\sample.peb\", \"test\\\\test\\\\other.peb\", BACKWARD_SLASH));\n    assertEquals(null,\n        PathUtils.resolveRelativePath(\"test\\\\sample.peb\", \"test\\\\test\\\\other.peb\", BACKWARD_SLASH));\n\n  }\n\n\n  @Test\n  void testRelativePathResolutionMixedStyle1() {\n    assertEquals(\"test/test/sample.peb\",\n        PathUtils.resolveRelativePath(\".\\\\sample.peb\", \"test/test/\", FORWARD_SLASH));\n    assertEquals(\"test/test/sample.peb\",\n        PathUtils.resolveRelativePath(\".\\\\sample.peb\", \"test/test/other.peb\", FORWARD_SLASH));\n    assertEquals(\"test/sample.peb\",\n        PathUtils.resolveRelativePath(\"..\\\\sample.peb\", \"test/test/\", FORWARD_SLASH));\n    assertEquals(\"test/sample.peb\",\n        PathUtils.resolveRelativePath(\"..\\\\sample.peb\", \"test/test/other.peb\", FORWARD_SLASH));\n    assertEquals(null,\n        PathUtils.resolveRelativePath(\"test\\\\sample.peb\", \"test/test/other.peb\", FORWARD_SLASH));\n  }\n\n  @Test\n  void testRelativePathResolutionMixedStyle2() {\n    assertEquals(\"test\\\\test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\"./sample.peb\", \"test\\\\test\\\\\", BACKWARD_SLASH));\n    assertEquals(\"test\\\\test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\"./sample.peb\", \"test\\\\test\\\\other.peb\", BACKWARD_SLASH));\n    assertEquals(\"test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\"../sample.peb\", \"test\\\\test\\\\\", BACKWARD_SLASH));\n    assertEquals(\"test\\\\sample.peb\",\n        PathUtils.resolveRelativePath(\"../sample.peb\", \"test\\\\test\\\\other.peb\", BACKWARD_SLASH));\n    assertEquals(null,\n        PathUtils.resolveRelativePath(\"test/sample.peb\", \"test\\\\test\\\\other.peb\", BACKWARD_SLASH));\n  }\n}\n"
  },
  {
    "path": "pebble/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n  <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n    <encoder>\n      <pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>\n    </encoder>\n  </appender>\n\n  <root level=\"INFO\">\n    <appender-ref ref=\"STDOUT\"/>\n  </root>\n  \n  <logger name=\"io.pebbletemplates.template.tests\" level=\"INFO\"/>\n  <logger name=\"io.pebbletemplates.lexer\" level=\"INFO\"/>\n  \n</configuration>"
  },
  {
    "path": "pebble/src/test/resources/security/allowedMethods.properties",
    "content": "java.lang.Object.toString()\njava.lang.Object.hashCode()\njava.lang.Object.equals(java.lang.Object)\n\nio.pebbletemplates.pebble.attributes.methodaccess.Foo.getX()\nio.pebbletemplates.pebble.attributes.methodaccess.Foo.setX(java.lang.String)\nio.pebbletemplates.pebble.attributes.methodaccess.Foo.toString()\nio.pebbletemplates.pebble.attributes.methodaccess.Foo.hashCode()\nio.pebbletemplates.pebble.attributes.methodaccess.Foo.equals(java.lang.Object)"
  },
  {
    "path": "pebble/src/test/resources/security/unsafeMethods.properties",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n\njava.lang.Object.wait()\njava.lang.Object.wait(long)\njava.lang.Object.wait(long,int)\njava.lang.Object.notify()\njava.lang.Object.notifyAll()\njava.lang.Object.getClass()\n\njava.lang.Class.getClassLoader()\njava.lang.Class.newInstance()\njava.lang.Class.forName(java.lang.String)\njava.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)\n\njava.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)\n\njava.lang.reflect.Method.invoke(java.lang.Object,[Ljava.lang.Object;)\n\njava.lang.reflect.Field.set(java.lang.Object,java.lang.Object)\njava.lang.reflect.Field.setBoolean(java.lang.Object,boolean)\njava.lang.reflect.Field.setByte(java.lang.Object,byte)\njava.lang.reflect.Field.setChar(java.lang.Object,char)\njava.lang.reflect.Field.setDouble(java.lang.Object,double)\njava.lang.reflect.Field.setFloat(java.lang.Object,float)\njava.lang.reflect.Field.setInt(java.lang.Object,int)\njava.lang.reflect.Field.setLong(java.lang.Object,long)\njava.lang.reflect.Field.setShort(java.lang.Object,short)\n\njava.lang.reflect.AccessibleObject.setAccessible([Ljava.lang.reflect.AccessibleObject;,boolean)\njava.lang.reflect.AccessibleObject.setAccessible(boolean)\n\njava.lang.Thread.getContextClassLoader()\njava.lang.Thread.interrupt()\njava.lang.Thread.join()\njava.lang.Thread.join(long)\njava.lang.Thread.join(long,int)\njava.lang.Thread.resume()\njava.lang.Thread.run()\njava.lang.Thread.setContextClassLoader(java.lang.ClassLoader)\njava.lang.Thread.setDaemon(boolean)\njava.lang.Thread.setName(java.lang.String)\njava.lang.Thread.setPriority(int)\njava.lang.Thread.sleep(long)\njava.lang.Thread.sleep(long,int)\njava.lang.Thread.start()\njava.lang.Thread.stop()\njava.lang.Thread.suspend()\n\njava.lang.ThreadGroup.allowThreadSuspension(boolean)\njava.lang.ThreadGroup.destroy()\njava.lang.ThreadGroup.interrupt()\njava.lang.ThreadGroup.resume()\njava.lang.ThreadGroup.setDaemon(boolean)\njava.lang.ThreadGroup.setMaxPriority(int)\njava.lang.ThreadGroup.stop()\n\njava.lang.Runtime.addShutdownHook(java.lang.Thread)\njava.lang.Runtime.exec(java.lang.String)\njava.lang.Runtime.exec([Ljava.lang.String;)\njava.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;)\njava.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;,java.io.File)\njava.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;)\njava.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;,java.io.File)\njava.lang.Runtime.exit(int)\njava.lang.Runtime.halt(int)\njava.lang.Runtime.load(java.lang.String)\njava.lang.Runtime.loadLibrary(java.lang.String)\njava.lang.Runtime.removeShutdownHook(java.lang.Thread)\n\njava.lang.System.exit(int)\njava.lang.System.load(java.lang.String)\njava.lang.System.loadLibrary(java.lang.String)\njava.lang.System.setErr(java.io.PrintStream)\njava.lang.System.setIn(java.io.InputStream)\njava.lang.System.setOut(java.io.PrintStream)\njava.lang.System.setProperties(java.util.Properties)\njava.lang.System.setProperty(java.lang.String,java.lang.String)\njava.lang.System.setSecurityManager(java.lang.SecurityManager)"
  },
  {
    "path": "pebble/src/test/resources/template-tests/DoubleNestedIfStatement.peb",
    "content": "text before for loop followed by blank line\n{% for item in items %}\n{% if item.itemType equals \"ITEM_TYPE1\" -%}\nItem 1\n{% elseif item.itemType equals \"ITEM_TYPE2\" -%}\nItem 2\n{% elseif item.itemType equals \"ITEM_TYPE3\" -%}\nItem 3\n{% elseif item.itemType equals \"ITEM_TYPE4\" -%}\n{% if item.hasPrefix -%}\nItem Prefix\n{% endif -%}\nItem 4\n{% endif -%}\n{% endfor %}\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/DoubleNestedIfStatement.txt",
    "content": "text before for loop followed by blank line\n\nItem 1\n\nItem 2\n\nItem 3\n\nItem Prefix\nItem 4\n\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/ForLoopWithNestedIfStatementAndMacro.peb",
    "content": "text before for loop followed by blank line\n{% for item in items %}\n{% if item.itemType equals \"ITEM_TYPE1\" -%}\n{{ item.name }}\n{% else -%}\n{{ itemStatement(item) }}\n{% endif -%}\n{% endfor %}\ntext after for loop preceded by blank line\n\n\n{%- macro itemStatement(item) -%}\n{%- if item.hasPrefix == true -%}\nPrefix Text\n{% endif -%}\n{{ item.name }}\n{%- endmacro -%}"
  },
  {
    "path": "pebble/src/test/resources/template-tests/ForLoopWithNestedIfStatementAndMacro.txt",
    "content": "text before for loop followed by blank line\n\nItem 1\n\nItem 2\n\nItem 3\n\nPrefix Text\nItem 4\n\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/NestedIfStatementWithOneElseIfStatements.peb",
    "content": "text before for loop followed by blank line\n{% for item in items %}\n{% if item.itemType equals \"ITEM_TYPE1\" -%}\nItem 1\n{% elseif item.itemType equals \"ITEM_TYPE2\" -%}\nItem 2\n{%- endif -%}\n{% endfor -%}\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/NestedIfStatementWithOneElseIfStatements.txt",
    "content": "text before for loop followed by blank line\n\nItem 1\n\nItem 2\n\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/NestedIfStatementWithThreeElseIfStatements.peb",
    "content": "text before for loop followed by blank line\n{% for item in items %}\n{% if item.itemType equals \"ITEM_TYPE1\" -%}\nItem 1\n{% elseif item.itemType equals \"ITEM_TYPE2\" -%}\nItem 2\n{% elseif item.itemType equals \"ITEM_TYPE3\" -%}\nItem 3\n{% elseif item.itemType equals \"ITEM_TYPE4\" -%}\nItem 4\n{% endif -%}\n{% endfor %}\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/NestedIfStatementWithThreeElseIfStatements.txt",
    "content": "text before for loop followed by blank line\n\nItem 1\n\nItem 2\n\nItem 3\n\nItem 4\n\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/NestedIfStatementWithTwoElseIfStatements.peb",
    "content": "text before for loop followed by blank line\n{% for item in items %}\n{% if item.itemType equals \"ITEM_TYPE1\" -%}\nItem 1\n{% elseif item.itemType equals \"ITEM_TYPE2\" -%}\nItem 2\n{% elseif item.itemType equals \"ITEM_TYPE3\" -%}\nItem 3\n{%- endif -%}\n{% endfor %}\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/template-tests/NestedIfStatementWithTwoElseIfStatements.txt",
    "content": "text before for loop followed by blank line\n\nItem 1\n\nItem 2\n\nItem 3\n\ntext after for loop preceded by blank line"
  },
  {
    "path": "pebble/src/test/resources/templates/cache/cache1/template.cache.peb",
    "content": "{# used by CacheTest.templatesWithSameNameOverridingCache #}\ncache1"
  },
  {
    "path": "pebble/src/test/resources/templates/cache/cache2/template.cache.peb",
    "content": "{# used by CacheTest.templatesWithSameNameOverridingCache #}\ncache2"
  },
  {
    "path": "pebble/src/test/resources/templates/cache/template.cacheChild.peb",
    "content": "{# used by CacheTest.ensureChildTemplateNotCached #}\n{% extends 'templates/cache/template.cacheParent.peb' %}\n{% block title %}child{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/cache/template.cacheParent.peb",
    "content": "{# used by CacheTest.ensureChildTemplateNotCached #}\n{% block title %}parent{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/cache/template.base.peb",
    "content": "BEFORE BASE\n{% block embedBlock1 %}\nEMBED BASE\n{% endblock %}\nAFTER BASE"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/cache/template1.peb",
    "content": "{% embed './template.base.peb' %}\n{% block 'embedBlock1' %}\nEMBED OVERRIDE\n{% endblock %}\n{% endembed %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/cache/template2.peb",
    "content": "{% include './template.base.peb' %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/notes.md",
    "content": "This is a complete example using the `embed` tag with lots of different scenarios for using the embed, and specifically\nhow it interacts with the main template hierarchy. Ideally, the embed is a completely isolated template which maintains\nits own template hierarchy, and blocks are resolved entirely within its isolated hierarchy. Outside of the embed, blocks\nare resolved related to the normal hierarchy, and there is no mixing of the two. \n\nThe majority of these tests were compared to Twig for fidelity, using https://twigfiddle.com/.\n\n## Unit Tests\n\n- test2 - Bare minimum, can an `embed` include a template. Without overriding a block, an `embed` should work identical \n    to an `include`\n- test0 - Can an `embed` override a block in an included template\n- test1 - Can an `embed` override a block in an included template, using Pebble's string block name syntax\n- test3 - Variables can be passed to the included template's scope using `with`. Overridden blocks use the scope of the \n    included template, not the parent template, and can use Pebble's string block name syntax\n- test4 - Variables can be passed to the included template's scope using `with`. Overridden blocks use the scope of the \n    included template, not the parent template\n- test5 - Multiple blocks can be overridden at once. When using the `block()` function, looked-up blocks will also use\n    the overridden block rather than the one in the included template\n- test6 - When using the `parent()` function, looked-up blocks will use the parent block in the included template\n\n## Integration Tests\n\n- test7 - The template being embedded may itself extend other templates. This may go many layers deep. \n- test8 - The parent templates of the embedded template may override blocks from their parents\n- test9 - The parent templates of the embedded template may override blocks from their parents. Different templates may\n    override different blocks simultaneously\n- test10 - The embed tab can override blocks at the same time that parent templates do, and they are all resolved \n    properly\n- test11 - `block()` and `parent()` functions can be used within the `embed` tag's overridden blocks to refer to it's \n    parent template blocks, which are resolved with overrides properly\n- test12 - An embed tag can be used in a template that itself extends another template. The same parent template may\n    be used in either case\n- test13 - The `extends` tag and `embed` tag maintain different template hierarchies. Blocks are resolved using the \n    proper hierarchy for the scope the `block()` and `parent()` functions are used in\n- test14 - Blocks do not bleed from the parent template hierarchy into the embed template hierarchy.\n\n## Error-checking Tests\n\n- test15 - Error thrown if content is added to the embed tag (instead of a block inside the embed)\n- test16 - Error thrown if content is added before a block in the the embed tag\n- test17 - Error thrown if content is added after a block in the the embed tag\n- test18 - Error thrown if content is added between blocks in the the embed tag"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test0/template.base.peb",
    "content": "BEFORE BASE\n{% block embedBlock1 %}\nEMBED BASE\n{% endblock %}\nAFTER BASE"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test0/template.peb",
    "content": "BEFORE ALL\n{% embed './template.base.peb' %}{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test0/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE\nEMBED BASE\nAFTER BASE\nAFTER ALL\n"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test0/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE\nEMBED BASE\nAFTER BASE\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test1/template.base.peb",
    "content": "BEFORE BASE\n{% block embedBlock1 %}\nEMBED BASE\n{% endblock %}\nAFTER BASE"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test1/template.peb",
    "content": "BEFORE ALL\n{% embed './template.base.peb' %}\n{% block embedBlock1 %}\nEMBED OVERRIDE\n{% endblock %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test1/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE\nEMBED OVERRIDE\nAFTER BASE\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test1/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE\nEMBED OVERRIDE\nAFTER BASE\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test10/template.base.1.peb",
    "content": "{% extends 'template.base.2.peb' %}\n{% block block1 %}\nblock1 base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test10/template.base.2.peb",
    "content": "{% extends 'template.base.3.peb' %}\n{% block block2 %}\nblock2 base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test10/template.base.3.peb",
    "content": "BEFORE BASE 3\n{% block block1 %}\nblock1 base 3\n{% endblock %}\n{% block block2 %}\nblock2 base 3\n{% endblock %}\n{% block block3 %}\nblock3 base 3\n{% endblock %}\n{% block block4 %}\nblock4 base 3\n{% endblock %}\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test10/template.peb",
    "content": "BEFORE ALL\n{% embed 'template.base.1.peb' %}\n{% block block4 %}\nblock4 embed\n{% endblock %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test10/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\nblock4 embed\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test10/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\nblock4 embed\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test11/template.base.1.peb",
    "content": "{% extends 'template.base.2.peb' %}\n{% block block1 %}\nblock1 base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test11/template.base.2.peb",
    "content": "{% extends 'template.base.3.peb' %}\n{% block block2 %}\nblock2 base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test11/template.base.3.peb",
    "content": "BEFORE BASE 3\n{% block block1 %}\nblock1 base 3\n{% endblock %}\n{% block block2 %}\nblock2 base 3\n{% endblock %}\n{% block block3 %}\nblock3 base 3\n{% endblock %}\n{% block block4 %}\nblock4 base 3\n{% endblock %}\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test11/template.peb",
    "content": "BEFORE ALL\n{% embed 'template.base.1.peb' %}\n{% block block4 %}\nblock4 embed\nparent={{ parent() }}\nblock1={{ block('block1') }}\nblock2={{ block('block2') }}\nblock3={{ block('block3') }}\n{% endblock %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test11/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\nblock4 embed\nparent=block4 base 3\n\nblock1=block1 base 1\n\nblock2=block2 base 2\n\nblock3=block3 base 3\n\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test11/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\nblock4 embed\nparent=block4 base 3\nblock1=block1 base 1\nblock2=block2 base 2\nblock3=block3 base 3\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test12/template.base.1.peb",
    "content": "{% extends 'template.base.2.peb' %}\n{% block block1 %}\nblock1 base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test12/template.base.2.peb",
    "content": "{% extends 'template.base.3.peb' %}\n{% block block2 %}\nblock2 base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test12/template.base.3.peb",
    "content": "BEFORE BASE 3\n{% block block1 %}\nblock1 base 3\n{% endblock %}\n{% block block2 %}\nblock2 base 3\n{% endblock %}\n{% block block3 %}\nblock3 base 3\n{% endblock %}\n{% block block4 %}\nblock4 base 3\n{% endblock %}\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test12/template.peb",
    "content": "{% extends 'template.base.1.peb' %}\n    {% block block4 %}\n    BEFORE ALL\n    {% embed 'template.base.1.peb' %}\n    {% block block4 %}\n    block4 embed\n    parent={{ parent() }}\n    block1={{ block('block1') }}\n    block2={{ block('block2') }}\n    block3={{ block('block3') }}\n    {% endblock %}\n    {% endembed %}\n\n    AFTER ALL\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test12/template.result.twig.txt",
    "content": "BEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\n    BEFORE ALL\n    BEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\n    block4 embed\n    parent=block4 base 3\n\n    block1=block1 base 1\n\n    block2=block2 base 2\n\n    block3=block3 base 3\n\n    AFTER BASE 3\n    AFTER ALL\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test12/template.result.txt",
    "content": "BEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\n    BEFORE ALL\n    BEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\n    block4 embed\n    parent=block4 base 3\n    block1=block1 base 1\n    block2=block2 base 2\n    block3=block3 base 3\n    AFTER BASE 3\n    AFTER ALL\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.embedbase.1.peb",
    "content": "{% extends 'template.embedbase.2.peb' %}\n{% block block1 %}\nblock1 embed base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.embedbase.2.peb",
    "content": "{% extends 'template.embedbase.3.peb' %}\n{% block block2 %}\nblock2 embed base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.embedbase.3.peb",
    "content": "BEFORE EMBED BASE 3\n{% block block1 %}\nblock1 embed base 3\n{% endblock %}\n{% block block2 %}\nblock2 embed base 3\n{% endblock %}\n{% block block3 %}\nblock3 embed base 3\n{% endblock %}\n{% block block4 %}\nblock4 embed base 3\n{% endblock %}\nAFTER EMBED BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.extendsbase.1.peb",
    "content": "{% extends 'template.extendsbase.2.peb' %}\n{% block block1 %}\nblock1 extends base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.extendsbase.2.peb",
    "content": "{% extends 'template.extendsbase.3.peb' %}\n{% block block2 %}\nblock2 extends base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.extendsbase.3.peb",
    "content": "BEFORE EXTENDS BASE 3\n{% block block1 %}\nblock1 extends base 3\n{% endblock %}\n{% block block2 %}\nblock2 extends base 3\n{% endblock %}\n{% block block3 %}\nblock3 extends base 3\n{% endblock %}\n{% block block4 %}\nblock4 extends base 3\n{% endblock %}\nAFTER EXTENDS BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.peb",
    "content": "{% extends 'template.extendsbase.1.peb' %}\n    {% block block4 %}\n    BEFORE ALL\n    {% embed 'template.embedbase.1.peb' %}\n    {% block block4 %}\n    block4 embed embed\n    parent={{ parent() }}\n    block1={{ block('block1') }}\n    block2={{ block('block2') }}\n    block3={{ block('block3') }}\n    {% endblock %}\n    {% endembed %}\n\n    AFTER ALL\n\n    block4 extends extends\n    parent={{ parent() }}\n    block1={{ block('block1') }}\n    block2={{ block('block2') }}\n    block3={{ block('block3') }}\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.result.twig.txt",
    "content": "BEFORE EXTENDS BASE 3\nblock1 extends base 1\nblock2 extends base 2\nblock3 extends base 3\n    BEFORE ALL\n    BEFORE EMBED BASE 3\nblock1 embed base 1\nblock2 embed base 2\nblock3 embed base 3\n    block4 embed embed\n    parent=block4 embed base 3\n\n    block1=block1 embed base 1\n\n    block2=block2 embed base 2\n\n    block3=block3 embed base 3\n\n    AFTER EMBED BASE 3\n    AFTER ALL\n\n    block4 extends extends\n    parent=block4 extends base 3\n\n    block1=block1 extends base 1\n\n    block2=block2 extends base 2\n\n    block3=block3 extends base 3\n\nAFTER EXTENDS BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test13/template.result.txt",
    "content": "BEFORE EXTENDS BASE 3\nblock1 extends base 1\nblock2 extends base 2\nblock3 extends base 3\n    BEFORE ALL\n    BEFORE EMBED BASE 3\nblock1 embed base 1\nblock2 embed base 2\nblock3 embed base 3\n    block4 embed embed\n    parent=block4 embed base 3\n    block1=block1 embed base 1\n    block2=block2 embed base 2\n    block3=block3 embed base 3\n    AFTER EMBED BASE 3\n    AFTER ALL\n\n    block4 extends extends\n    parent=block4 extends base 3\n    block1=block1 extends base 1\n    block2=block2 extends base 2\n    block3=block3 extends base 3\nAFTER EXTENDS BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test14/template.embedbase.1.peb",
    "content": "{% block block1 %}\nblock1 embed base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test14/template.extendsbase.1.peb",
    "content": "{% extends 'template.extendsbase.2.peb' %}\n{% block block1 %}\nblock1 extends base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test14/template.extendsbase.2.peb",
    "content": "{% extends 'template.extendsbase.3.peb' %}\n{% block block2 %}\nblock2 extends base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test14/template.extendsbase.3.peb",
    "content": "BEFORE EXTENDS BASE 3\n{% block block1 %}\nblock1 extends base 3\n{% endblock %}\n{% block block2 %}\nblock2 extends base 3\n{% endblock %}\n{% block block3 %}\nblock3 extends base 3\n{% endblock %}\n{% block block4 %}\nblock4 extends base 3\n{% endblock %}\nAFTER EXTENDS BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test14/template.peb",
    "content": "{% extends 'template.extendsbase.1.peb' %}\n    {% block block4 %}\n    BEFORE ALL\n    {% embed 'template.embedbase.1.peb' %}\n    {% block block4 %}\n    block4 embed embed\n    parent={{ parent() }}\n    block1={{ block('block1') }}\n    block2={{ block('block2') }}\n    block3={{ block('block3') }}\n    {% endblock %}\n    {% endembed %}\n\n    AFTER ALL\n\n    block4 extends extends\n    parent={{ parent() }}\n    block1={{ block('block1') }}\n    block2={{ block('block2') }}\n    block3={{ block('block3') }}\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test14/template.result.twig.txt",
    "content": "BEFORE EXTENDS BASE 3\nblock1 extends base 1\nblock2 extends base 2\nblock3 extends base 3\n    BEFORE ALL\n    block1 embed base 1\n\n    AFTER ALL\n\n    block4 extends extends\n    parent=block4 extends base 3\n\n    block1=block1 extends base 1\n\n    block2=block2 extends base 2\n\n    block3=block3 extends base 3\n\nAFTER EXTENDS BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test14/template.result.txt",
    "content": "BEFORE EXTENDS BASE 3\nblock1 extends base 1\nblock2 extends base 2\nblock3 extends base 3\n    BEFORE ALL\n    block1 embed base 1\n\n    AFTER ALL\n\n    block4 extends extends\n    parent=block4 extends base 3\n    block1=block1 extends base 1\n    block2=block2 extends base 2\n    block3=block3 extends base 3\nAFTER EXTENDS BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test15/template.base.peb",
    "content": "{% block block1 %}\nblock1 embed base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test15/template.error.txt",
    "content": "A template that extends another one cannot include content outside blocks. Did you forget to put the content inside a {% block %} tag? (template.peb:3)"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test15/template.peb",
    "content": "{% embed 'template.base.peb' %}\n    instead of blocks\n{% endembed %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test16/template.base.peb",
    "content": "{% block block1 %}\nblock1 embed base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test16/template.error.txt",
    "content": "A template that extends another one cannot include content outside blocks. Did you forget to put the content inside a {% block %} tag? (template.peb:3)"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test16/template.peb",
    "content": "{% embed 'template.base.peb' %}\n    before blocks\n    {% block block1 %}\n    overridden\n    {% endblock %}\n{% endembed %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test17/template.base.peb",
    "content": "{% block block1 %}\nblock1 embed base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test17/template.error.txt",
    "content": "A template that extends another one cannot include content outside blocks. Did you forget to put the content inside a {% block %} tag? (template.peb:6)"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test17/template.peb",
    "content": "{% embed 'template.base.peb' %}\n    {% block block1 %}\n    overridden\n    {% endblock %}\n    after blocks\n{% endembed %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test18/template.base.peb",
    "content": "{% block block1 %}\nblock1 embed base\n{% endblock %}\n{% block block2 %}\nblock2 embed base\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test18/template.error.txt",
    "content": "A template that extends another one cannot include content outside blocks. Did you forget to put the content inside a {% block %} tag? (template.peb:6)"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test18/template.peb",
    "content": "{% embed 'template.base.peb' %}\n    {% block block1 %}\n    overridden\n    {% endblock %}\n    between blocks\n    {% block block2 %}\n    overridden\n    {% endblock %}\n{% endembed %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test2/template.base.peb",
    "content": "BEFORE BASE\n{% block embedBlock1 %}\nEMBED BASE\n{% endblock %}\nAFTER BASE"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test2/template.peb",
    "content": "BEFORE ALL\n{% embed './template.base.peb' %}\n{% block 'embedBlock1' %}\nEMBED OVERRIDE\n{% endblock %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test2/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE\nEMBED OVERRIDE\nAFTER BASE\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test3/template.base.peb",
    "content": "BEFORE BASE [{{ foo }}]\n{% block embedBlock1 %}\nEMBED BASE [{{ foo }}]\n{% endblock %}\nAFTER BASE [{{ foo }}]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test3/template.peb",
    "content": "BEFORE ALL [{{ foo }}]\n{% embed './template.base.peb' with {'foo': 'NEWFOO', 'bar': 'NEWBAR'} %}\n{% block 'embedBlock1' %}\nEMBED OVERRIDE [{{ bar }}]\n{% endblock %}\n{% endembed %}\n\nAFTER ALL [{{ bar }}]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test3/template.result.txt",
    "content": "BEFORE ALL [FOO]\nBEFORE BASE [NEWFOO]\nEMBED OVERRIDE [NEWBAR]\nAFTER BASE [NEWFOO]\nAFTER ALL [BAR]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test4/template.base.peb",
    "content": "BEFORE BASE [{{ foo }}]\n{% block embedBlock1 %}\nEMBED BASE [{{ foo }}]\n{% endblock %}\nAFTER BASE [{{ foo }}]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test4/template.peb",
    "content": "BEFORE ALL [{{ foo }}]\n{% embed './template.base.peb' with {'foo': 'NEWFOO', 'bar': 'NEWBAR'} %}\n{% block embedBlock1 %}\nEMBED OVERRIDE [{{ bar }}]\n{% endblock %}\n{% endembed %}\n\nAFTER ALL [{{ bar }}]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test4/template.result.twig.txt",
    "content": "BEFORE ALL [FOO]\nBEFORE BASE [NEWFOO]\nEMBED OVERRIDE [NEWBAR]\nAFTER BASE [NEWFOO]\nAFTER ALL [BAR]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test4/template.result.txt",
    "content": "BEFORE ALL [FOO]\nBEFORE BASE [NEWFOO]\nEMBED OVERRIDE [NEWBAR]\nAFTER BASE [NEWFOO]\nAFTER ALL [BAR]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test5/template.base.peb",
    "content": "BEFORE BASE [{{ foo }}]\n{% block embedBlock1 %}\nEMBED BASE [{{ foo }}]\n{% endblock %}\n{% block embedBlock2 %}\nEMBED BASE [{{ foo }}]\n{% endblock %}\nAFTER BASE [{{ foo }}]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test5/template.peb",
    "content": "BEFORE ALL\n{% embed './template.base.peb' with {'foo': 'NEWFOO', 'bar': 'NEWBAR'} %}\n{% block embedBlock1 %}\nEMBED OVERRIDE [{{ bar }}]\n{% endblock %}\n{% block embedBlock2 %}\n{{ block('embedBlock1') }}\n{% endblock %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test5/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE [NEWFOO]\nEMBED OVERRIDE [NEWBAR]\nEMBED OVERRIDE [NEWBAR]\n\nAFTER BASE [NEWFOO]\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test5/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE [NEWFOO]\nEMBED OVERRIDE [NEWBAR]\nEMBED OVERRIDE [NEWBAR]\nAFTER BASE [NEWFOO]\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test6/template.base.peb",
    "content": "BEFORE BASE [{{ foo }}]\n{% block embedBlock1 %}\nEMBED BASE [{{ foo }}]\n{% endblock %}\nAFTER BASE [{{ foo }}]"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test6/template.peb",
    "content": "BEFORE ALL\n{% embed './template.base.peb' with {'foo': 'NEWFOO', 'bar': 'NEWBAR'} %}\n{% block embedBlock1 %}\nEMBED OVERRIDE [{{ bar }}]\n(\nparent={{ parent() }}\n)\n{% endblock %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test6/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE [NEWFOO]\nEMBED OVERRIDE [NEWBAR]\n(\nparent=EMBED BASE [NEWFOO]\n)\nAFTER BASE [NEWFOO]\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test6/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE [NEWFOO]\nEMBED OVERRIDE [NEWBAR]\n(\nparent=EMBED BASE [NEWFOO]\n)\nAFTER BASE [NEWFOO]\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test7/template.base.1.peb",
    "content": "{% extends 'template.base.2.peb' %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test7/template.base.2.peb",
    "content": "{% extends 'template.base.3.peb' %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test7/template.base.3.peb",
    "content": "BEFORE BASE 3\n{% block block1 %}\nblock1 base 3\n{% endblock %}\n{% block block2 %}\nblock2 base 3\n{% endblock %}\n{% block block3 %}\nblock3 base 3\n{% endblock %}\n{% block block4 %}\nblock4 base 3\n{% endblock %}\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test7/template.peb",
    "content": "BEFORE ALL\n{% embed 'template.base.1.peb' %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test7/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 3\nblock2 base 3\nblock3 base 3\nblock4 base 3\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test7/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 3\nblock2 base 3\nblock3 base 3\nblock4 base 3\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test8/template.base.1.peb",
    "content": "{% extends 'template.base.2.peb' %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test8/template.base.2.peb",
    "content": "{% extends 'template.base.3.peb' %}\n{% block block2 %}\nblock2 base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test8/template.base.3.peb",
    "content": "BEFORE BASE 3\n{% block block1 %}\nblock1 base 3\n{% endblock %}\n{% block block2 %}\nblock2 base 3\n{% endblock %}\n{% block block3 %}\nblock3 base 3\n{% endblock %}\n{% block block4 %}\nblock4 base 3\n{% endblock %}\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test8/template.peb",
    "content": "BEFORE ALL\n{% embed 'template.base.1.peb' %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test8/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 3\nblock2 base 2\nblock3 base 3\nblock4 base 3\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test8/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 3\nblock2 base 2\nblock3 base 3\nblock4 base 3\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test9/template.base.1.peb",
    "content": "{% extends 'template.base.2.peb' %}\n{% block block1 %}\nblock1 base 1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test9/template.base.2.peb",
    "content": "{% extends 'template.base.3.peb' %}\n{% block block2 %}\nblock2 base 2\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test9/template.base.3.peb",
    "content": "BEFORE BASE 3\n{% block block1 %}\nblock1 base 3\n{% endblock %}\n{% block block2 %}\nblock2 base 3\n{% endblock %}\n{% block block3 %}\nblock3 base 3\n{% endblock %}\n{% block block4 %}\nblock4 base 3\n{% endblock %}\nAFTER BASE 3"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test9/template.peb",
    "content": "BEFORE ALL\n{% embed 'template.base.1.peb' %}\n{% endembed %}\n\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test9/template.result.twig.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\nblock4 base 3\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/embed/test9/template.result.txt",
    "content": "BEFORE ALL\nBEFORE BASE 3\nblock1 base 1\nblock2 base 2\nblock3 base 3\nblock4 base 3\nAFTER BASE 3\nAFTER ALL"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.block.peb",
    "content": "{# used by CoreFunctionsTest.testBlockFunction #}\n{% block title %}Default Title{% endblock title %}\n\n{{ block(\"title\") }}"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.child.peb",
    "content": "{# used by CoreFunctionsTest.testParentFunction #}\n{% extends \"templates/function/template.parent.peb\" %}\nchild text\n{% block head %}\n\t{{ parent() }}\n\tchild head\n{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.childThenParentThenMacro.peb",
    "content": "{# used by CoreFunctionsTest.testParentThenMacro #}\n{% extends \"templates/function/template.parentThenMacro.peb\" %}\n{% block head %}{{ parent() }}{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.childWithContext.peb",
    "content": "{# used by CoreFunctionsTest.testParentBlockHasAccessToContext #}\n{% extends \"templates/function/template.parentAccessContext.peb\" %}\n{% block head %}{% set foo = 'bar' %}{{ parent() }}{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.parent.peb",
    "content": "{# used by CoreFunctionsTest.testParentFunction #}\nparent text\n{% block head %}\n\tparent head\n{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.parentAccessContext.peb",
    "content": "{# used by CoreFunctionsTest.testParentBlockHasAccessToContext #}\n{% block head %}{{ foo }}{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.parentThenMacro.peb",
    "content": "{# used by CoreFunctionsTest.testParentThenMacro #}\n{% block head %}{{ returnItem('test') }}{% endblock head %}\n{% macro returnItem(item) %}{{ item }}{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/function/template.subchild.peb",
    "content": "{# used by CoreFunctionsTest.testParentFunctionWithTwoLevels #}\n{% extends \"templates/function/template.child.peb\" %}\nsub child text\n{% block head %}\n\t{{ parent() }}\n\tsub child head\n{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/loader/template.loaderTest.peb",
    "content": "{#\n\tUsed by:\n\t\t- LoaderTest.testClassLoaderLoaderWithNestedTemplate\n#}\nSUCCESS"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/from.peb",
    "content": "Hello\n\n{% from \"templates/macros/macro.peb\" import test as macro_test, test2 as macro_test2 %}\nCall 1: {{ macro_test() }}\n\nCall 3: {{ macro_test2() }}\n\n{% include \"templates/macros/include.peb\" %}\n\n\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/import.as.peb",
    "content": "Hello\n\n{% import \"templates/macros/macro.peb\" as macro %}\nCall 1: {{ macro.test() }}\n\n{% include \"templates/macros/include.peb\" %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/include.peb",
    "content": "Include\n{% import \"templates/macros/macro.peb\" %}\nCall 2: {{ test() }}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/index.peb",
    "content": "Hello\n\n{% import \"templates/macros/macro.peb\" %}\nCall 1: {{ test() }}\n\n{% include \"templates/macros/include.peb\" %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/invalid.from.peb",
    "content": "Hello\n\n{% from \"templates/macros/macro.peb\" import test as macro_test test2 as macro_test2 %}\nCall 1: {{ macro_test() }}\n\nCall 3: {{ macro_test2() }}\n\n{% include \"templates/macros/include.peb\" %}\n\n\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/invalid.from.sameAlias.peb",
    "content": "Hello\n\n{% from \"templates/macros/macro.peb\" import test as macro_test%}\n{% from \"templates/macros/macro.peb\" import test2 as macro_test%}\nCall 1: {{ macro_test() }}\n\nCall 3: {{ macro_test2() }}\n\n{% include \"templates/macros/include.peb\" %}\n\n\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/invalid.from.unknownMacro.peb",
    "content": "Hello\n\n{% from \"templates/macros/macro.peb\" import iDontExist as macro_test %}\nCall 1: {{ macro_test() }}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/invalid.import.as.sameAlias.peb",
    "content": "Hello\n\n{% import \"templates/macros/macro.peb\" as macro %}\n{% import \"templates/macros/macro.peb\" as macro %}\nCall 1: {{ macro.test() }}\n\n{% include \"templates/macros/include.peb\" %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/invalid.macro.peb",
    "content": "{# line 1 #}\n{{ macroWhichDoesNotExists() }}\n{# line 3 #}"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/macro.peb",
    "content": "{% macro test() %}\nMacro-Output: {{ \"test\" | testfilter }}\n{% endmacro %}\n\n{% macro test2() %}\nMacro-Output: {{ \"test\" | testfilter }}\n{% endmacro %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/setVariableBase.peb",
    "content": "{{ unit }}\n{% import 'templates/macros/setVariableMacro.peb' %}\n{{ infantry() }}\n{{ unit }}"
  },
  {
    "path": "pebble/src/test/resources/templates/macros/setVariableMacro.peb",
    "content": "{% macro infantry() %}\n{% set unit = 'infantry' %}\n{{ unit }}\n{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/subdirectory1/template.backwardslashes.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeIncludeWithSubdirectory\n#}\n{% include '..\\subdirectory2\\template.relativeinclude2.peb' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/subdirectory1/template.forwardslashes.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeIncludeWithSubdirectory\n#}\n{% include '../subdirectory2/template.relativeinclude2.peb' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/subdirectory2/template.relativeinclude2.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeIncludeWithSubdirectory\n#}\nincluded"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/template.relativeextends1.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeExtends\n#}\n{% extends './template.relativeextends2.peb' %}\n\n{% block test %}\n\toverridden\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/template.relativeextends2.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeExtends\n#}\n<div>\n{% block test %}\n\tmy test\n{% endblock %}\n</div>"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/template.relativeimport1.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeImports\n#}\n{% import \"./template.relativeimport2.peb\" %}\n{{ input(\"company\", \"forcorp\", \"text\") }}"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/template.relativeimport2.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeImports\n#}\n{% macro input(name, value, type) %}\n\t<input name=\"{{ name }}\" value=\"{{ value }}\" type=\"{{ type }}\" />\n{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/template.relativeinclude1.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeInclude\n#}\n{% include './template.relativeinclude2.peb' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/relativepath/template.relativeinclude2.peb",
    "content": "{#\n\tused by:\n\t\tTestRelativePath.testRelativeInclude\n#}\nincluded"
  },
  {
    "path": "pebble/src/test/resources/templates/single-block/template.renderextendedblock1.peb",
    "content": "{# used by RenderSingleBlockTest.testRenderSingleExtendedBlock #}\n{% extends \"./template.renderextendedblock2.peb\" %}\n\nText in the template that should not appear in either block\n\n{% block content_a %}Block A{% endblock %}\n{% block content_b %}Block B{% endblock %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/single-block/template.renderextendedblock2.peb",
    "content": "{# used by RenderSingleBlockTest.testRenderSingleExtendedBlock #}\n\nText in the parent template that should not appear in either block\n\n{% block container_a %}{% block content_a %}{% endblock %} extended{% endblock %}\n{% block container_b %}{% block content_b %}{% endblock %} extended{% endblock %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/template.autoescapeInclude1.peb",
    "content": "{# \n\tused by:\n\t\tEscaperExtensionTest.testAutoEscapingInclude\n#}\n{% include './template.autoescapeInclude2.peb' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.autoescapeInclude2.peb",
    "content": "{# \n\tused by:\n\t\tEscaperExtensionTest.testAutoEscapingInclude\n#}\n<{{ text }}>"
  },
  {
    "path": "pebble/src/test/resources/templates/template.autoescapeParent1.peb",
    "content": "{# \n\tused by:\n\t\tEscaperExtensionTest.testAutoEscapingInclude\n#}\n{% extends 'templates/template.autoescapeParent2.peb' %}\n{% block nav %}{{ parent() }}{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.autoescapeParent2.peb",
    "content": "{# \n\tused by:\n\t\tEscaperExtensionTest.testAutoEscapingInclude\n#}\n{% block nav %}<{{ text }}>{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.child.peb",
    "content": "{# \n\tused by: \n\t\t- InheritanceTest.testMultiLevelInheritance \n#}\n{% extends \"./template.parent.peb\" %}\n\n{% block head %}\n\tCHILD HEAD\n{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.concurrent1.peb",
    "content": "{# \n\tused by:\n\t\tCacheTest.testConcurrentCacheHitting\n\t\tConcurrentTest.testThreadSafeCompilationOfMultipleTemplates \n#}\n{{ test.a }}:{{ test.b }}:{{ test.c }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.concurrent2.peb",
    "content": "{# \n\tused by ConcurrentTest.testThreadSafeCompilationOfMultipleTemplates \n#}\n{{ test.a }}:{{ test.b }}:{{ test.c }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.dynamicChild.peb",
    "content": "{# \n\tused by: \n\t\t- InheritanceTest.testDynamicInheritance \n#}\n{% extends extendNumberOne? \"./template.dynamicParent1.peb\" : \"./template.dynamicParent2.peb\" %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.dynamicParent1.peb",
    "content": "{# \n\tused by: \n\t\t- InheritanceTest.testDynamicInheritance \n#}\n{% block head %}ONE{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.dynamicParent2.peb",
    "content": "{# \n\tused by: \n\t\t- InheritanceTest.testDynamicInheritance \n#}\n{% block head %}TWO{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.errorReporting.peb",
    "content": "{# used by ErrorReportingTest.testLineNumberErrorReportingDuringEvaluation #}\n\n\n{# comment #}\n\ncontent here\n{% block content %}\n\t{{ parent() }} {# error on line 7 due to no parent template#}\n{% endblock %}\ncontent here too"
  },
  {
    "path": "pebble/src/test/resources/templates/template.escapeCharactersInText.peb",
    "content": "{# used by CompilerTest.testEscapeCharactersText #}\ntest multiple \nlines        and tabs         and of course:\n                        a bunch of escape characters!\n' \\' /\\\\//\\/\\////\\\\\\ \\b  \\f \\n \\r '\n\\\\/\\/\"         \" \\\" \\\\                \\t"
  },
  {
    "path": "pebble/src/test/resources/templates/template.general.peb",
    "content": "{# used by CompilerTest.testCompilationMutexIsAlwaysReleased #}\ntest"
  },
  {
    "path": "pebble/src/test/resources/templates/template.grandfather.peb",
    "content": "{# \n\tused by: \n\t\t- CoreTagsTest.testBlock\n\t\t- CacheTest.templateCachedButBytecodeCleared\n\t\t- InheritanceTest.testSimpleInheritance  \n\t\t- InheritanceTest.testMultiLevelInheritance \n#}\nGRANDFATHER TEXT ABOVE HEAD\n\n{% block head %}\n\tGRANDFATHER HEAD\n{% endblock %}\n\nGRANDFATHER TEXT BELOW HEAD AND ABOVE FOOT\n\n{% block foot %}\n\tGRANDFATHER FOOT\n{% endblock foot %}\n\nGRANDFATHER TEXT BELOW FOOT"
  },
  {
    "path": "pebble/src/test/resources/templates/template.import.dynamic.macro_ajax.peb",
    "content": "{# used by CoreTagsTest.testDynamicImport #}\n{% macro render_form() %}\najax macro\n{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.import.dynamic.macro_classic.peb",
    "content": "{# used by CoreTagsTest.testDynamicImport #}\n{% macro render_form() %}\nclassic macro\n{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.import.dynamic.peb",
    "content": "{# used by CoreTagsTest.testDynamicImport #}\n{# depending on the boolean variable modern, different macro files will be #}\n{# imported. Both macro files provide a macro named render_form. #}\n{% import modern ? \"templates/template.import.dynamic.macro_ajax.peb\" \n        : \"templates/template.import.dynamic.macro_classic.peb\" %}\n{{ render_form() }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.importWithinBlock.peb",
    "content": "{# used by CoreTagsTest.testImportWithinBlock #}\n{% block content %}\n{% import \"templates/template.macro1.peb\" %}\n{{ input(\"company\", \"forcorp\", \"text\") }}\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.include.dynamic.adminFooter.peb",
    "content": "{# used by CoreTagsTest.testDynamicInclude #}\nadmin footer"
  },
  {
    "path": "pebble/src/test/resources/templates/template.include.dynamic.defaultFooter.peb",
    "content": "{# used by CoreTagsTest.testDynamicInclude #}\ndefault footer"
  },
  {
    "path": "pebble/src/test/resources/templates/template.include.dynamic.peb",
    "content": "{# used by CoreTagsTest.testDynamicInclude #}\n{# depending on the boolean variable admin, different templates will be included. #}\n{% include admin ? \"templates/template.include.dynamic.adminFooter.peb\" \n        : \"templates/template.include.dynamic.defaultFooter.peb\" %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.include1.peb",
    "content": "{# used by CoreTagsTest.testInclude #}\n{% include \"./template.include2.peb\" %}\nTEMPLATE1\n{% include \"./template.include2.peb\" %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.include2.peb",
    "content": "{# used by CoreTagsTest.testInclude #}\nTEMPLATE2\n"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeInheritance1.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.includeInheritance\n#}\n{% include './template.includeInheritance2.peb' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeInheritance2.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.includeInheritance\n#}\n{% extends './template.includeInheritance3.peb' %}\n{% block content %}success{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeInheritance3.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.includeInheritance\n#}\n{% block content %}fail{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeOverrideBlock.peb",
    "content": "{# used by CoreTagsTest.testIncludeOverridesBlocks #}\n{% include \"templates/template.includeOverrideBlock2.peb\" %}\n{% block content %}\nONE\n{% endblock %}\n{% include \"templates/template.includeOverrideBlock2.peb\" %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeOverrideBlock2.peb",
    "content": "{# used by CoreTagsTest.testIncludeOverridesBlocks #}\n{% block content %}\nTWO\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeOverrideVariable1.peb",
    "content": "{# used by CoreTagsTest.testIncludeOverridesVariable #}\n{% set one = 'one (base)' %}\n{% set two = 'two (base)' %}\n{% include \"templates/template.includeOverrideVariable2.peb\" with {\"one\": \"one (overridden)\", \"two\": null} %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeOverrideVariable2.peb",
    "content": "{# used by CoreTagsTest.testIncludeOverridesVariable #}\nOne: {{ one }}\n\nTwo: {{ two }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includePropagatesContext.peb",
    "content": "{# used by CoreTagsTest.testIncludePropagatesContext #}\n{% include \"templates/template.includePropagatesContext2.peb\" %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includePropagatesContext2.peb",
    "content": "{# used by CoreTagsTest.testIncludePropagatesContext #}\n{{ name }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeWithParameter1.peb",
    "content": "{# used by IncludeWithParameterTest.testIncludeWithMapParameters #}\n{% include './template.includeWithParameter2.peb' with {\n        'simple': 'simple-value',\n        'contextVariable': contextVariable,\n        'level': level + 1,\n        'map': {\n            'position': 'left',\n            'contextVariable': contextVariable\n        }}\n   %}\nlevel-main:{{ level }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeWithParameter2.peb",
    "content": "simple:{{ simple }}\ncontextVariable:{{ contextVariable }}\nmap.position:{{ map.position }}\nmap.contextVariable:{{ map.contextVariable }}\nlevel:{{ level }}\n{% set level = level + 1 %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeWithParameterNotIsolated1.peb",
    "content": "{# used by IncludeWithParameterTest.testIncludeWithMapParametersNotIsolated #}\n{% set foo = 'bar' %}\n{% include './template.includeWithParameterNotIsolated2.peb' with { 'foo' : 'baz' } %}\n{{ foo }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeWithParameterNotIsolated2.peb",
    "content": "{{ foo }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeWithParameterObject1.peb",
    "content": "{# used by IncludeWithParameterTest.testIncludeWithParameterObject #}\n{% include './template.includeWithParameterObject2.peb' with { 'sub' : object } %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeWithParameterObject2.peb",
    "content": "Hello {{ sub.title }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.includeWithinBlock.peb",
    "content": "{# used by CoreTagsTest.testIncludeWithinBlock #}\n{% block content %}\n{% include \"templates/template.include2.peb\" %}\nTEMPLATE1\n{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.loaderTest.peb",
    "content": "{#\n\tUsed by:\n\t\t- LoaderTest.testClassLoaderLoader\n\t\t- LoaderTest.testFileLoader\n#}\nSUCCESS"
  },
  {
    "path": "pebble/src/test/resources/templates/template.loaderTest.peb.suffix",
    "content": "{#\n\tUsed by:\n\t\t- LoaderTest.testClassLoaderLoader\n\t\t- LoaderTest.testFileLoader\n#}\nSUCCESS"
  },
  {
    "path": "pebble/src/test/resources/templates/template.macro.child.peb",
    "content": "{# used by CoreTagsTest.testImportInChildTemplateOutsideOfBlock #}\n{% extends \"templates/template.macro.parent.peb\" %}\n\n{% import \"templates/template.macro1.peb\" %}\n\n{% block content %}{{ input(\"company\", \"forcorp\", \"text\") }}{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.macro.exploding.peb",
    "content": "{{ m9() }}\n{% macro m0() %}1234567890{% endmacro %}\n{% macro m1() %}{{m0()}}{{m0()}}{{m0()}}{{m0()}}{{m0()}}{{m0()}}{{m0()}}{{m0()}}{{m0()}}{{m0()}}{% endmacro %}\n{% macro m2() %}{{m1()}}{{m1()}}{{m1()}}{{m1()}}{{m1()}}{{m1()}}{{m1()}}{{m1()}}{{m1()}}{{m1()}}{% endmacro %}\n{% macro m3() %}{{m2()}}{{m2()}}{{m2()}}{{m2()}}{{m2()}}{{m2()}}{{m2()}}{{m2()}}{{m2()}}{{m2()}}{% endmacro %}\n{% macro m4() %}{{m3()}}{{m3()}}{{m3()}}{{m3()}}{{m3()}}{{m3()}}{{m3()}}{{m3()}}{{m3()}}{{m3()}}{% endmacro %}\n{% macro m5() %}{{m4()}}{{m4()}}{{m4()}}{{m4()}}{{m4()}}{{m4()}}{{m4()}}{{m4()}}{{m4()}}{{m4()}}{% endmacro %}\n{% macro m6() %}{{m5()}}{{m5()}}{{m5()}}{{m5()}}{{m5()}}{{m5()}}{{m5()}}{{m5()}}{{m5()}}{{m5()}}{% endmacro %}\n{% macro m7() %}{{m6()}}{{m6()}}{{m6()}}{{m6()}}{{m6()}}{{m6()}}{{m6()}}{{m6()}}{{m6()}}{{m6()}}{% endmacro %}\n{% macro m8() %}{{m7()}}{{m7()}}{{m7()}}{{m7()}}{{m7()}}{{m7()}}{{m7()}}{{m7()}}{{m7()}}{{m7()}}{% endmacro %}\n{% macro m9() %}{{m8()}}{{m8()}}{{m8()}}{{m8()}}{{m8()}}{{m8()}}{{m8()}}{{m8()}}{{m8()}}{{m8()}}{% endmacro %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/template.macro.parent.peb",
    "content": "{# used by CoreTagsTest.testImportInChildTemplateOutsideOfBlock #}\n{% block content %}{% endblock %}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/template.macro1.peb",
    "content": "{# used by CoreTagsTest.testMacro #}\n{{ input(\"company\", \"google\", \"text\") }}\n{% macro input(name, value, type) %}\n\t<input name=\"{{ name }}\" value=\"{{ value }}\" type=\"{{ type }}\" />\n{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.macro2.peb",
    "content": "{# used by CoreTagsTest.testMacroFromAnotherFile #}\n{% import \"templates/template.macro1.peb\" %}\n{{ input(\"company\", \"forcorp\", \"text\") }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.macro3.peb",
    "content": "{# used by CoreTagsTest.testMacroBeingFiltered #}\n{{ returnItem(\"hello\") | upper }}\n\n{% macro returnItem(item) %}{{ item }}{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.macroDouble.peb",
    "content": "{# used by CoreTagsTest.testMacroInvokedTwice #}\n{{ repeat('one') }}\n{{ repeat('two') }}\n{% macro repeat(input) %}\n{{ input }}\n{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parallelInclude1.peb",
    "content": "{# used by CoreTagsTest.testIncludeWithinParallelTag #}\n{% parallel %}{% include \"./template.parallelInclude2.peb\" %}{% endparallel %}\n\nTEMPLATE1\n{% include \"./template.parallelInclude2.peb\" %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parallelInclude2.peb",
    "content": "{# used by CoreTagsTest.testIncludeWithinParallelTag #}\n{{ slowObject.first }}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parallelParsing1.peb",
    "content": "output in 1: a|{% delay 100 %}output in 1: b|{% delay 200 %}output in 1: c"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parallelParsing2.peb",
    "content": "output in 2: a|{% delay 50 %}output in 2: b|{% delay 100 %}output in 2: c"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parallelWithImport.peb",
    "content": "{# used by CoreTagsTest.testParallelWithImport #}\n{% import 'templates/template.parallelWithImport2.peb' %}\n{% parallel %}{{ test() }}{% endparallel %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parallelWithImport2.peb",
    "content": "{# used by CoreTagsTest.testParallelWithImport #}\n{% macro test() %}success{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parent.peb",
    "content": "{# \n\tused by: \n\t\t- CacheTest.templateCachedButBytecodeCleared\n\t\t- InheritanceTest.testSimpleInheritance \n\t\t- InheritanceTest.testMultiLevelInheritance \n#}\n{% extends \"./template.grandfather.peb\" %}\n\n{% block head %}\n\tPARENT HEAD\n{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.parent2.peb",
    "content": "{# used by CacheTest.templateCachedButBytecodeCleared #}\n{% extends \"./template.grandfather.peb\" %}\n\n{% block head %}\n\tPARENT HEAD\n{% endblock head %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.set.child.peb",
    "content": "{# used by CoreTagsTest.testSetInChildTemplateOutsideOfBlock #}\n{% extends \"templates/template.set.parent.peb\" %}\n{% set childDefinedVariable = 'SUCCESS' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.set.parent.peb",
    "content": "{# used by CoreTagsTest.testSetInChildTemplateOutsideOfBlock #}\n{{ childDefinedVariable }}\n"
  },
  {
    "path": "pebble/src/test/resources/templates/template.skipGenerationBlock1.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.skipGenerationBlock\n#}\n{% extends './template.skipGenerationBlock2.peb' %}\n{% block content %}success{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.skipGenerationBlock2.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.skipGenerationBlock\n#}\n{% extends './template.skipGenerationBlock3.peb' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.skipGenerationBlock3.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.skipGenerationBlock\n#}\n{% block content %}{% endblock %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.skipGenerationMacro1.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.skipGenerationMacro\n#}\n{% extends './template.skipGenerationMacro2.peb' %}\n{% macro content() %}success{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.skipGenerationMacro2.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.skipGenerationMacro\n#}\n{% extends './template.skipGenerationMacro3.peb' %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.skipGenerationMacro3.peb",
    "content": "{# \n\tused by \n\t\t- CoreTagsTest.skipGenerationMacro\n#}\n{{ content() }}\n{% macro content() %}{% endmacro %}"
  },
  {
    "path": "pebble/src/test/resources/templates/template.strictModeComplexExpression.peb",
    "content": "<div>\n\t<p>{{ test.test }}</p>\n</div>"
  },
  {
    "path": "pebble/src/test/resources/templates/template.strictModeSimpleExpression.peb",
    "content": "<div>\n\t<p>{{ test }}</p>\n</div>"
  },
  {
    "path": "pebble/src/test/resources/testMessages.properties",
    "content": "greeting=Hello\ngreeting.someone=Hello, {0}"
  },
  {
    "path": "pebble/src/test/resources/testMessages_es_US.properties",
    "content": "greeting=Hola\ngreeting.someone=Hola, {0}\ngreeting.specialchars=Hola español"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <artifactId>pebble-spring</artifactId>\n    <groupId>io.pebbletemplates</groupId>\n    <version>4.1.2-SNAPSHOT</version>\n  </parent>\n\n  <artifactId>pebble-legacy-spring-boot-starter</artifactId>\n\n  <name>Pebble Spring Boot 3 Starter</name>\n  <description>Spring Boot 3 starter for Pebble Template Engine</description>\n  <url>http://pebbletemplates.io</url>\n\n  <properties>\n    <java.version>17</java.version>\n    <boot.version>3.5.14</boot.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-web</artifactId>\n      <version>${boot.version}</version>\n      <optional>true</optional>\n      <exclusions>\n        <exclusion>\n          <groupId>ch.qos.logback</groupId>\n          <artifactId>logback-classic</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-webflux</artifactId>\n      <version>${boot.version}</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>io.pebbletemplates</groupId>\n      <artifactId>pebble-spring6</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-configuration-processor</artifactId>\n      <version>${boot.version}</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-autoconfigure-processor</artifactId>\n      <version>${boot.version}</version>\n      <optional>true</optional>\n    </dependency>\n\n    <!-- TEST -->\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-test</artifactId>\n      <version>${boot.version}</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifestEntries>\n              <Automatic-Module-Name>io.pebbletemplates.spring.boot</Automatic-Module-Name>\n            </manifestEntries>\n          </archive>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/AbstractPebbleConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nabstract class AbstractPebbleConfiguration {\n\n  protected String stripLeadingSlash(String value) {\n    if (value == null) {\n      return null;\n    }\n    if (value.startsWith(\"/\")) {\n      return value.substring(1);\n    }\n    return value;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleAutoConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;\nimport io.pebbletemplates.pebble.extension.Extension;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.pebble.node.ForNode;\nimport io.pebbletemplates.pebble.node.expression.UnaryMinusExpression;\nimport io.pebbletemplates.spring.extension.SpringExtension;\nimport java.util.List;\n\nimport org.springframework.aot.hint.MemberCategory;\nimport org.springframework.aot.hint.RuntimeHints;\nimport org.springframework.aot.hint.RuntimeHintsRegistrar;\nimport org.springframework.aot.hint.TypeReference;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.lang.Nullable;\n\n@AutoConfiguration\n@ConditionalOnClass(PebbleEngine.class)\n@EnableConfigurationProperties(PebbleProperties.class)\n@Import({PebbleServletWebConfiguration.class, PebbleReactiveWebConfiguration.class})\npublic class PebbleAutoConfiguration extends AbstractPebbleConfiguration {\n\n  @Bean\n  @ConditionalOnMissingBean(name = \"pebbleLoader\")\n  public Loader<?> pebbleLoader(PebbleProperties properties) {\n    ClasspathLoader loader = new ClasspathLoader();\n    loader.setCharset(properties.getCharsetName());\n    // classpath loader does not like leading slashes in resource paths\n    loader.setPrefix(this.stripLeadingSlash(properties.getPrefix()));\n    loader.setSuffix(properties.getSuffix());\n    return loader;\n  }\n\n  @Bean\n  @ConditionalOnMissingBean\n  public SpringExtension springExtension(MessageSource messageSource) {\n    return new SpringExtension(messageSource);\n  }\n\n  @Bean\n  @ConditionalOnMissingBean(name = \"pebbleEngine\")\n  public PebbleEngine pebbleEngine(PebbleProperties properties,\n      Loader<?> pebbleLoader,\n      SpringExtension springExtension,\n      @Nullable List<Extension> extensions,\n      @Nullable MethodAccessValidator methodAccessValidator) {\n    PebbleEngine.Builder builder = new PebbleEngine.Builder();\n    builder.loader(pebbleLoader);\n    builder.extension(springExtension);\n    if (extensions != null && !extensions.isEmpty()) {\n      builder.extension(extensions.toArray(new Extension[extensions.size()]));\n    }\n    if (!properties.isCache()) {\n      builder.cacheActive(false);\n    }\n    if (properties.getDefaultLocale() != null) {\n      builder.defaultLocale(properties.getDefaultLocale());\n    }\n    builder.strictVariables(properties.isStrictVariables());\n    builder.greedyMatchMethod(properties.isGreedyMatchMethod());\n    if (methodAccessValidator != null) {\n      builder.methodAccessValidator(methodAccessValidator);\n    }\n    return builder.build();\n  }\n}\n\nclass PebbleTemplatesHints implements RuntimeHintsRegistrar {\n\n    @Override\n    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {\n        hints.reflection()\n                .registerType(TypeReference.of(UnaryMinusExpression.class),\n                        hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))\n                .registerType(TypeReference.of(ForNode.LoopVariables.class),\n                        hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS,\n                                MemberCategory.DECLARED_CLASSES, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,\n                                MemberCategory.INVOKE_DECLARED_METHODS));\n    }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleProperties.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport java.util.Locale;\nimport org.springframework.boot.autoconfigure.template.AbstractTemplateViewResolverProperties;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@ConfigurationProperties(\"pebble\")\npublic class PebbleProperties extends AbstractTemplateViewResolverProperties {\n\n  public static final String DEFAULT_PREFIX = \"/templates/\";\n  public static final String DEFAULT_SUFFIX = \".peb\";\n\n  private Locale defaultLocale;\n  private boolean strictVariables;\n  private boolean greedyMatchMethod;\n\n  public PebbleProperties() {\n    super(DEFAULT_PREFIX, DEFAULT_SUFFIX);\n    this.setCache(true);\n  }\n\n  public Locale getDefaultLocale() {\n    return this.defaultLocale;\n  }\n\n  public void setDefaultLocale(Locale defaultLocale) {\n    this.defaultLocale = defaultLocale;\n  }\n\n  public boolean isStrictVariables() {\n    return this.strictVariables;\n  }\n\n  public void setStrictVariables(boolean strictVariables) {\n    this.strictVariables = strictVariables;\n  }\n\n  public boolean isGreedyMatchMethod() {\n    return this.greedyMatchMethod;\n  }\n\n  public void setGreedyMatchMethod(boolean greedyMatchMethod) {\n    this.greedyMatchMethod = greedyMatchMethod;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleReactiveWebConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.spring.reactive.PebbleReactiveViewResolver;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication(type = Type.REACTIVE)\nclass PebbleReactiveWebConfiguration extends AbstractPebbleConfiguration {\n\n  @Bean\n  @ConditionalOnMissingBean(name = \"pebbleReactiveViewResolver\")\n  PebbleReactiveViewResolver pebbleReactiveViewResolver(PebbleProperties properties,\n                                                        PebbleEngine pebbleEngine) {\n    String prefix = properties.getPrefix();\n    if (pebbleEngine.getLoader() instanceof ClasspathLoader) {\n      // classpathloader doesn't like leading slashes in paths\n      prefix = this.stripLeadingSlash(properties.getPrefix());\n    }\n    PebbleReactiveViewResolver resolver = new PebbleReactiveViewResolver(pebbleEngine);\n    resolver.setPrefix(prefix);\n    resolver.setSuffix(properties.getSuffix());\n    resolver.setViewNames(properties.getViewNames());\n    resolver.setRequestContextAttribute(properties.getRequestContextAttribute());\n    return resolver;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleServletWebConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.spring.servlet.PebbleViewResolver;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication(type = Type.SERVLET)\nclass PebbleServletWebConfiguration extends AbstractPebbleConfiguration {\n\n  @Bean\n  @ConditionalOnMissingBean(name = \"pebbleViewResolver\")\n  PebbleViewResolver pebbleViewResolver(PebbleProperties properties,\n                                        PebbleEngine pebbleEngine) {\n    PebbleViewResolver pvr = new PebbleViewResolver(pebbleEngine);\n    properties.applyToMvcViewResolver(pvr);\n    if (pebbleEngine.getLoader() instanceof ClasspathLoader) {\n      // classpathloader doesn't like leading slashes in paths\n      pvr.setPrefix(this.stripLeadingSlash(properties.getPrefix()));\n    }\n\n    return pvr;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleTemplateAvailabilityProvider.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;\nimport org.springframework.core.env.Environment;\nimport org.springframework.core.io.ResourceLoader;\nimport org.springframework.util.ClassUtils;\n\nimport static org.springframework.core.io.ResourceLoader.CLASSPATH_URL_PREFIX;\n\npublic class PebbleTemplateAvailabilityProvider implements TemplateAvailabilityProvider {\n\n  @Override\n  public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,\n      ResourceLoader resourceLoader) {\n    if (ClassUtils.isPresent(PebbleEngine.class.getCanonicalName(), classLoader)) {\n      String prefix = environment.getProperty(\"pebble.prefix\", PebbleProperties.DEFAULT_PREFIX);\n      String suffix = environment.getProperty(\"pebble.suffix\", PebbleProperties.DEFAULT_SUFFIX);\n      return resourceLoader.getResource(CLASSPATH_URL_PREFIX + prefix + view + suffix).exists();\n    }\n    return false;\n  }\n\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/package-info.java",
    "content": "/**\n * Auto-configuration for Pebble Template Engine.\n */\npackage io.pebbletemplates.boot.autoconfigure;"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring/aot.factories",
    "content": "org.springframework.aot.hint.RuntimeHintsRegistrar=\\\nio.pebbletemplates.boot.autoconfigure.PebbleTemplatesHints"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "io.pebbletemplates.boot.autoconfigure.PebbleAutoConfiguration"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.factories",
    "content": "# Template availability providers\norg.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\\\nio.pebbletemplates.boot.autoconfigure.PebbleTemplateAvailabilityProvider"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/AppConfig.java",
    "content": "package io.pebbletemplates.boot;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Extension;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.support.ResourceBundleMessageSource;\nimport org.springframework.web.servlet.LocaleResolver;\nimport org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n@Configuration(proxyBeanMethods = false)\npublic class AppConfig {\n  @Bean\n  public MessageSource messageSource() {\n    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();\n    messageSource.setBasename(\"messages\");\n    messageSource.setFallbackToSystemLocale(false);\n    return messageSource;\n  }\n\n  @Bean\n  public LocaleResolver localeResolver() {\n    return new AcceptHeaderLocaleResolver();\n  }\n\n  @Bean\n  public Extension testExtension() {\n    return new TestExtension();\n  }\n\n  public static class TestExtension extends AbstractExtension {\n\n    @Override\n    public Map<String, Function> getFunctions() {\n      Map<String, Function> functions = new HashMap<String, Function>();\n      functions.put(\"testFunction\", new TestFunction());\n      return functions;\n    }\n\n    public static class TestFunction implements Function {\n\n      @Override\n      public List<String> getArgumentNames() {\n        return Collections.emptyList();\n      }\n\n      @Override\n      public Object execute(Map<String, Object> args, PebbleTemplate self,\n                            EvaluationContext context, int lineNumber) {\n        return \"Tested!\";\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/Application.java",
    "content": "package io.pebbletemplates.boot;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n  public static void main(String[] args) {\n    SpringApplication.run(Application.class, args);\n  }\n}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/Controllers.java",
    "content": "package io.pebbletemplates.boot;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.server.WebSession;\n\n@Controller\npublic class Controllers {\n\n  @RequestMapping(\"/hello.action\")\n  public String hello() {\n    return \"hello\";\n  }\n\n  @RequestMapping(\"/index.action\")\n  public String index() {\n    return \"index\";\n  }\n\n  @RequestMapping(\"/contextPath.action\")\n  public String contextPath() {\n    return \"contextPath\";\n  }\n\n  @RequestMapping(\"/extensions.action\")\n  public String extensions() {\n    return \"extensions\";\n  }\n\n  @RequestMapping(\"/beans.action\")\n  public String beans() {\n    return \"beans\";\n  }\n\n  @RequestMapping(\"/session.action\")\n  public String session(WebSession session) {\n    session.getAttributes().put(\"foo\", \"bar\");\n    return \"session\";\n  }\n\n  @RequestMapping(\"/response.action\")\n  public String response() {\n    return \"responseObject\";\n  }\n\n}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/Foo.java",
    "content": "package io.pebbletemplates.boot;\n\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class Foo {\n\n  public String value = \"bar\";\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/NonWebAppTests.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.boot.Application;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\n\nimport java.io.StringWriter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@SpringBootTest(classes = Application.class,\n        properties = \"spring.main.web-application-type=none\")\nclass NonWebAppTests {\n\n  @Autowired\n  private PebbleEngine pebbleEngine;\n\n  @Test\n  void testOk() throws Exception {\n    StringWriter sw = new StringWriter();\n    this.pebbleEngine.getTemplate(\"hello\").evaluate(sw);\n    assertThat(sw.toString() != null && !sw.toString().isEmpty()).isTrue();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/PebbleAutoConfigurationTest.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.attributes.methodaccess.BlacklistMethodAccessValidator;\nimport io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;\nimport io.pebbletemplates.pebble.attributes.methodaccess.NoOpMethodAccessValidator;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.spring.extension.SpringExtension;\nimport io.pebbletemplates.spring.reactive.PebbleReactiveView;\nimport io.pebbletemplates.spring.reactive.PebbleReactiveViewResolver;\nimport io.pebbletemplates.spring.servlet.PebbleView;\nimport io.pebbletemplates.spring.servlet.PebbleViewResolver;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.util.TestPropertyValues;\nimport org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;\nimport org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.web.reactive.result.view.UrlBasedViewResolver;\nimport org.springframework.web.servlet.view.AbstractTemplateViewResolver;\n\nimport java.util.Locale;\n\nimport static java.util.Locale.CHINESE;\nimport static java.util.Locale.FRENCH;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PebbleAutoConfigurationTest {\n\n  private static final Locale DEFAULT_LOCALE = CHINESE;\n  private static final Locale CUSTOM_LOCALE = FRENCH;\n  private AnnotationConfigServletWebApplicationContext webContext;\n\n  private AnnotationConfigReactiveWebApplicationContext reactiveWebContext;\n\n  @Test\n  void registerBeansForServletApp() {\n    this.loadWithServlet(null);\n    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()).isTrue();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().getMethodAccessValidator()).isInstanceOf(BlacklistMethodAccessValidator.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);\n  }\n\n  @Test\n  void registerCustomBeansForServletApp() {\n    this.loadWithServlet(CustomPebbleViewResolverConfiguration.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(0);\n    assertThat(this.webContext.getBeansOfType(AbstractTemplateViewResolver.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(CustomPebbleViewResolver.class)).hasSize(1);\n  }\n\n  @Test\n  void registerCompilerForServletApp() {\n    this.loadWithServlet(CustomPebbleEngineCompilerConfiguration.class);\n    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(CUSTOM_LOCALE);\n    assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isFalse();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()).isFalse();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().getMethodAccessValidator()).isInstanceOf(BlacklistMethodAccessValidator.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerCustomMethodAccessValidatorForServletApp() {\n    this.loadWithServlet(CustomMethodAccessValidatorConfiguration.class);\n    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()).isTrue();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().getMethodAccessValidator()).isInstanceOf(NoOpMethodAccessValidator.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerBeansForReactiveApp() {\n    this.loadWithReactive(null);\n    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions().getMethodAccessValidator()).isInstanceOf(BlacklistMethodAccessValidator.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerCustomBeansForReactiveApp() {\n    this.loadWithReactive(CustomPebbleReactiveViewResolverConfiguration.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(0);\n    assertThat(this.reactiveWebContext.getBeansOfType(UrlBasedViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(CustomPebbleReactiveViewResolver.class)).hasSize(1);\n  }\n\n  @Test\n  void registerCompilerForReactiveApp() {\n    this.loadWithReactive(CustomPebbleEngineCompilerConfiguration.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(CUSTOM_LOCALE);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isFalse();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()).isFalse();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions().getMethodAccessValidator()).isInstanceOf(BlacklistMethodAccessValidator.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerCustomMethodAccessValidatorForReactiveApp() {\n    this.loadWithReactive(CustomMethodAccessValidatorConfiguration.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions().getMethodAccessValidator()).isInstanceOf(NoOpMethodAccessValidator.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n  }\n\n  private void loadWithServlet(Class<?> config) {\n    this.webContext = new AnnotationConfigServletWebApplicationContext();\n    TestPropertyValues.of(\"pebble.prefix=classpath:/templates/\").applyTo(this.webContext);\n    TestPropertyValues.of(\"pebble.defaultLocale=zh\").applyTo(this.webContext);\n    TestPropertyValues.of(\"pebble.strictVariables=true\").applyTo(this.webContext);\n    TestPropertyValues.of(\"pebble.greedyMatchMethod=true\").applyTo(this.webContext);\n    if (config != null) {\n      this.webContext.register(config);\n    }\n    this.webContext.register(BaseConfiguration.class);\n    this.webContext.refresh();\n  }\n\n  private void loadWithReactive(Class<?> config) {\n    this.reactiveWebContext = new AnnotationConfigReactiveWebApplicationContext();\n    TestPropertyValues.of(\"pebble.prefix=classpath:/templates/\").applyTo(this.reactiveWebContext);\n    TestPropertyValues.of(\"pebble.defaultLocale=zh\").applyTo(this.reactiveWebContext);\n    TestPropertyValues.of(\"pebble.strictVariables=true\").applyTo(this.reactiveWebContext);\n    TestPropertyValues.of(\"pebble.greedyMatchMethod=true\").applyTo(this.reactiveWebContext);\n    if (config != null) {\n      this.reactiveWebContext.register(config);\n    }\n    this.reactiveWebContext.register(BaseConfiguration.class);\n    this.reactiveWebContext.refresh();\n  }\n\n  @Configuration(proxyBeanMethods = false)\n  @Import(PebbleAutoConfiguration.class)\n  protected static class BaseConfiguration {\n\n  }\n\n  @Configuration(proxyBeanMethods = false)\n  protected static class CustomPebbleEngineCompilerConfiguration {\n\n    @Bean\n    public PebbleEngine pebbleEngine() {\n      return new PebbleEngine.Builder().defaultLocale(CUSTOM_LOCALE).build();\n    }\n\n    @Bean\n    public SpringExtension customSpringExtension(MessageSource messageSource) {\n      return new SpringExtension(messageSource);\n    }\n\n  }\n\n    @Configuration(proxyBeanMethods = false)\n    protected static class CustomPebbleViewResolverConfiguration {\n      @Bean\n      public CustomPebbleViewResolver pebbleViewResolver() {\n            return new CustomPebbleViewResolver();\n        }\n\n    }\n\n    @Configuration(proxyBeanMethods = false)\n    protected static class CustomPebbleReactiveViewResolverConfiguration {\n\n      @Bean\n      public CustomPebbleReactiveViewResolver pebbleReactiveViewResolver() {\n          return new CustomPebbleReactiveViewResolver();\n      }\n\n    }\n\n    protected static class CustomPebbleViewResolver extends AbstractTemplateViewResolver {\n\n      public CustomPebbleViewResolver() {\n            this.setViewClass(PebbleView.class);\n        }\n\n    }\n\n    protected static class CustomPebbleReactiveViewResolver extends UrlBasedViewResolver {\n\n      public CustomPebbleReactiveViewResolver() {\n            this.setViewClass(PebbleReactiveView.class);\n        }\n\n    }\n\n  @Configuration(proxyBeanMethods = false)\n  protected static class CustomMethodAccessValidatorConfiguration {\n\n    @Bean\n    public MethodAccessValidator methodAccessValidator() {\n      return new NoOpMethodAccessValidator();\n    }\n  }\n\n}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/ReactiveAppTest.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.boot.Application;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.reactive.server.WebTestClient;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@SpringBootTest(classes = Application.class,\n        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,\n        properties = \"spring.main.web-application-type=reactive\")\nclass ReactiveAppTest {\n\n  @Autowired\n  private WebTestClient client;\n\n  @Test\n  void testOk() throws Exception {\n    String result = this.client.get().uri(\"/index.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hello Pebbleworld!\");\n  }\n\n  @Test\n  void testRequestAccess() throws Exception {\n    String result = this.client.get().uri(\"/contextPath.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"ctx path:/contextPath.action\");\n  }\n\n  @Test\n  void testEnglishHello() throws Exception {\n    String result = this.client.get().uri(\"/hello.action\")\n            .header(\"Accept-Language\", \"en\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hello Boot!\");\n  }\n\n  @Test\n  void testSpanishHello() throws Exception {\n    String result = this.client.get().uri(\"/hello.action\")\n            .header(\"Accept-Language\", \"es\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hola Boot!\");\n  }\n\n  @Test\n  void testAdditionalExtensions() throws Exception {\n    String result = this.client.get().uri(\"/extensions.action\")\n            .header(\"Accept-Language\", \"es\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hola Boot! Tested!\");\n  }\n\n  @Test\n  void testBeansAccess() throws Exception {\n    String result = this.client.get().uri(\"/beans.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"beans:bar\");\n  }\n\n  @Test\n  void testResponseAccess() throws Exception {\n    String result = this.client.get().uri(\"/response.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"response:200 OK\");\n  }\n\n  @Test\n  void testSessionAccess() {\n    String result = this.client.get().uri(\"/session.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"session:bar\");\n  }\n}\n\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/ServletAppTest.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.boot.Application;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.MvcResult;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\nimport org.springframework.web.context.WebApplicationContext;\n\nimport java.util.Locale;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)\nclass ServletAppTest {\n\n  @Autowired\n  private WebApplicationContext wac;\n\n  protected MockMvc mockMvc;\n\n  @BeforeEach\n  void setup() {\n    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();\n  }\n\n  @Test\n  void testOk() throws Exception {\n    this.mockMvc.perform(get(\"/index.action\")).andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hello Pebbleworld!\"));\n  }\n\n  @Test\n  void testRequestAccess() throws Exception {\n    MvcResult result = this.mockMvc.perform(get(\"/contextPath.action\")).andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)).andReturn();\n\n    assertThat(result.getResponse().getContentAsString()).isEqualTo(\"ctx path:\" + result.getRequest().getContextPath());\n  }\n\n  @Test\n  void testEnglishHello() throws Exception {\n    this.mockMvc.perform(get(\"/hello.action\").locale(Locale.forLanguageTag(\"en\")))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hello Boot!\"));\n  }\n\n  @Test\n  void testSpanishHello() throws Exception {\n    this.mockMvc.perform(get(\"/hello.action\").locale(Locale.forLanguageTag(\"es\")))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hola Boot!\"));\n  }\n\n  @Test\n  void testAdditionalExtensions() throws Exception {\n    this.mockMvc.perform(get(\"/extensions.action\").locale(Locale.forLanguageTag(\"es\")))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hola Boot! Tested!\"));\n  }\n\n  @Test\n  void testBeansAccess() throws Exception {\n    this.mockMvc.perform(get(\"/beans.action\"))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"beans:bar\"));\n  }\n\n  @Test\n  void testResponseAccess() throws Exception {\n    this.mockMvc.perform(get(\"/response.action\"))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"response:200\"));\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages.properties",
    "content": "hello.boot=Hello Boot!"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages_es.properties",
    "content": "hello.boot=Hola Boot!"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/beans.peb",
    "content": "beans:{{beans.foo.value}}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/contextPath.peb",
    "content": "ctx path:{{request.contextPath}}{{request.path}}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/extensions.peb",
    "content": "{{ message('hello.boot') }} {{ testFunction() }}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/hello.peb",
    "content": "{{ message('hello.boot') }}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/index.peb",
    "content": "{{'Hello Pebbleworld!'}}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/native-image.peb",
    "content": "{{ foo.bar }}\n\n{{ foo[\"bar\"] }}\n\n{{ bar[0] }}\n\n{{ \"If life gives you lemons, eat lemons.\" | upper | abbreviate(13) }}\n\n{{ max(13, highscore) }}\n\n{% for article in articles %}\n    <p>{{ article }}</p>\n{% else %}\n    <p> There are no articles. </p>\n{% endfor %}\n\n{% if category == \"news\" %}\n{{ news }}\n    {% elseif category == \"sports\" %}\n{{ sports }}\n    {% else %}\n    <p>Please select a category</p>\n{% endif %}\n\n{{ stringDate | date(existingFormat=\"yyyy-MMMM-d\", format=\"yyyy/MMMM/d\") }}\n\n{{ stringDate | date(\"yyyy/MMMM/d\", existingFormat=\"yyyy-MMMM-d\") }}\n\n{% set danger = \"<br>\" %}\n{{ danger }}\n\n{% set danger = \"<br>\" %}\n{{ danger | escape }}\n\n{# THIS IS A COMMENT #}\n{% if 3 is odd %}\n\t...\n{% endif %}\n\n{% for article in articles %}\n    <p>number: ; content: {{ article }}</p>\n{% else %}\n    <p> There are no articles. </p>\n{% endfor %}\n\n{% if name is not null %}\n\t...\n{% endif %}\n\n{{ folk ? \"yes\" : \"no\" }}\n\n{{ user.name | capitalize }}\n\n{% if user.name equals \"Mitchell\" %}\n\tDavid\n{% endif %}\n\n    {% if bar contains 2 %}\n        Two\n    {% endif %}\n\n{% if (3 is not even) and (2 is odd or 3 is even) %}\n\t...\n{% endif %}\n\n{% if user.telephone is empty %}\n\t...\n{% endif %}\n\n{% if user is not map %}\n\t...\n{% endif %}\n\n{% if user.email is null %}\n\tuser.email is null\n{% endif %}\n\n{% if users is iterable %}\n    {% for user in users %}\n\t\tUser: {{ user }}\n\t{% endfor %}\n{% endif %}\n\n{{ i18n(\"messages\",\"greetings\") }}\n\n{% for i in range(0, 36, 2) %}\n{{ i }},\n{% endfor %}\n\n{{ \"this is a long sentence.\" | abbreviate(7) }}\n\n{{ -7 | abs }}\n\n{{ \"dGVzdA==\" | base64decode }}\n\n{{ \"test\" | base64encode }}\n\n{{ \"article title\" | capitalize }}\n\n{{ \"July 24, 2001\" | date(\"yyyy-MM-dd\", existingFormat=\"MMMM dd, yyyy\") }}\n\n{{ user.telephone | default(\"No phone number\") }}\n\n{{ \"<div>\" | escape }}\n\n{{ users | first }}\n{# will output the first item in the collection named 'users' #}\n\n{{ 'Mitch' | first }}\n{# will output 'M' #}\n\n{{ names | join(',') }}\n{# will output: Alex,Joe,Bob #}\n\n{{ users | last }}\n{# will output the last item in the collection named 'users' #}\n\n{{ 'Mitch' | last }}\n{# will output 'h' #}\n\n{% if users|length > 10 %}\n    ...\n{% endif %}\n\n{{ \"THIS IS A LOUD SENTENCE\" | lower }}\n\n{{ 3.141592653 | numberformat(\"#.##\") }}\n\n{% set danger = \"<div>\" %}\n{{ danger | upper | raw }}\n{# ouptut: <DIV> #}\n\n {% set danger = \"<div>\" %}\n{{ danger | raw | upper }}\n{# output: &lt;DIV&gt; #}\n\n{{ \"I like %this% and %that%.\" | replace({'%this%': foo, '%that%': \"bar\"}) }}\n\n{% for user in users | reverse %} {{ user }} {% endfor %}\n\n{% for user in users | rsort %}\n    {{ user }}\n{% endfor %}\n\n{{ \"test\" | sha256 }}\n\n{{ ['apple', 'peach', 'pear', 'banana'] | slice(1,3) }}\n{# results in: [peach, pear] #}\n\n{{ 'Mitchell' | slice(1,3) }}\n{# results in: 'it' #}\n\n{% for user in users | sort %}\n    {{ user }}\n{% endfor %}\n\n{% set foo = \"one,two,three\" | split(',') %}\n{# foo contains ['one', 'two', 'three'] #}\n\n{% set foo = \"one,two,three,four,five\" | split(',', 3) %}\n{# foo contains ['one', 'two', 'three,four,five'] #}\n\n{{ \"article title\" | title }}\n\n{{ \"    This text has too much whitespace.    \" | trim }}\n\n{{ \"this is a quiet sentence.\" | upper }}\n\n{{ \"The string ü@foo-bar\" | urlencode }}\n\n{% block \"post\" %} content {% endblock %}\n\n{{ block(\"post\") }}\n\n{{ block(\"post\") }}\n\n\n{{ \"this is a long sentence.\" | abbreviate(7) }}\n\n{{ danger }} {# will be escaped by default #}\n{% autoescape false %}\n{{ danger }} {# will not be escaped #}\n{% endautoescape %}\n\n{{ danger }} {# will use the \"html\" escaping strategy #}\n{% autoescape \"js\" %}\n{{ danger }} {# will use the \"js\" escaping strategy #}\n{% endautoescape %}\n\n{% cache 'menu' %}\n    {% for item in items %}\n    {{ item.text }}\n        ....\n    {% endfor %}\n{% endcache %}\n\n{% filter upper %}\n\thello\n{% endfilter %}\n\n{% filter upper | escape %}\n\thello<br>\n{% endfilter %}\n\n{# output: 'HELLO&lt;br&gt;' #}\n\n{{ headerText }}\n    {% flush %}\n{{ content }}\n\n{% for user in users %}\n    {{ loop.index }}\n    {{ loop.length }}\n    {{ loop.first }}\n    {{ loop.last }}\n    {{ loop.revindex }}\n{% endfor %}\n\n{% include \"advertisement.peb\" with {\"foo\": \"bar\"} %}\n\n{% macro input(type=\"text\", name, value) %}\n\t<input type=\"{{ type }}\" name=\"{{ name }}\" value=\"{{ value }}\" />\n{% endmacro %}\n\n{{ input(name=\"country\") }}\n{# will output: <input type=\"text\" name=\"country\" value=\"\" /> #}\n\n{% verbatim %}\n\t{% for user in users %}\n\t\t{{ user.name }}\n\t{% endfor %}\n{% endverbatim %}\n"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/responseObject.peb",
    "content": "response:{{response.status}}{{response.statusCode}}"
  },
  {
    "path": "pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/session.peb",
    "content": "session:{{ session.attributes.foo }}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <artifactId>pebble-spring</artifactId>\n    <groupId>io.pebbletemplates</groupId>\n    <version>4.1.2-SNAPSHOT</version>\n  </parent>\n\n  <artifactId>pebble-spring-boot-starter</artifactId>\n\n  <name>Pebble Spring Boot 4 Starter</name>\n  <description>Spring Boot 4 starter for Pebble Template Engine</description>\n  <url>http://pebbletemplates.io</url>\n\n  <properties>\n    <java.version>17</java.version>\n    <boot.version>4.0.6</boot.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-webmvc</artifactId>\n      <version>${boot.version}</version>\n      <optional>true</optional>\n      <exclusions>\n        <exclusion>\n          <groupId>ch.qos.logback</groupId>\n          <artifactId>logback-classic</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-webflux</artifactId>\n      <version>${boot.version}</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>io.pebbletemplates</groupId>\n      <artifactId>pebble-spring7</artifactId>\n      <version>${project.version}</version>\n    </dependency>\n\n    <!-- TEST -->\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-webflux-test</artifactId>\n      <version>${boot.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-webmvc-test</artifactId>\n      <version>${boot.version}</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n      </plugin>\n      <plugin>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifestEntries>\n              <Automatic-Module-Name>io.pebbletemplates.spring.boot</Automatic-Module-Name>\n            </manifestEntries>\n          </archive>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/AbstractPebbleConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nabstract class AbstractPebbleConfiguration {\n\n  protected String stripLeadingSlash(String value) {\n    if (value == null) {\n      return null;\n    }\n    if (value.startsWith(\"/\")) {\n      return value.substring(1);\n    }\n    return value;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleAutoConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;\nimport io.pebbletemplates.pebble.extension.Extension;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.pebble.node.ForNode;\nimport io.pebbletemplates.pebble.node.expression.UnaryMinusExpression;\nimport io.pebbletemplates.spring.extension.SpringExtension;\nimport org.springframework.aot.hint.MemberCategory;\nimport org.springframework.aot.hint.RuntimeHints;\nimport org.springframework.aot.hint.RuntimeHintsRegistrar;\nimport org.springframework.aot.hint.TypeReference;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.lang.Nullable;\n\nimport java.util.List;\n\n@AutoConfiguration\n@ConditionalOnClass(PebbleEngine.class)\n@EnableConfigurationProperties(PebbleProperties.class)\n@Import({PebbleServletWebConfiguration.class, PebbleReactiveWebConfiguration.class})\npublic class PebbleAutoConfiguration extends AbstractPebbleConfiguration {\n\n  @Bean\n  @ConditionalOnMissingBean(name = \"pebbleLoader\")\n  public Loader<?> pebbleLoader(PebbleProperties properties) {\n    ClasspathLoader loader = new ClasspathLoader();\n    loader.setCharset(properties.getCharsetName());\n    // classpath loader does not like leading slashes in resource paths\n    loader.setPrefix(this.stripLeadingSlash(properties.getPrefix()));\n    loader.setSuffix(properties.getSuffix());\n    return loader;\n  }\n\n  @Bean\n  @ConditionalOnMissingBean\n  public SpringExtension springExtension(MessageSource messageSource) {\n    return new SpringExtension(messageSource);\n  }\n\n  @Bean\n  @ConditionalOnMissingBean(name = \"pebbleEngine\")\n  public PebbleEngine pebbleEngine(PebbleProperties properties,\n      Loader<?> pebbleLoader,\n      SpringExtension springExtension,\n      @Nullable List<Extension> extensions,\n      @Nullable MethodAccessValidator methodAccessValidator) {\n    PebbleEngine.Builder builder = new PebbleEngine.Builder();\n    builder.loader(pebbleLoader);\n    builder.extension(springExtension);\n    if (extensions != null && !extensions.isEmpty()) {\n      builder.extension(extensions.toArray(new Extension[extensions.size()]));\n    }\n    builder.cacheActive(properties.getServlet().isCache());\n    if (properties.getDefaultLocale() != null) {\n      builder.defaultLocale(properties.getDefaultLocale());\n    }\n    builder.strictVariables(properties.isStrictVariables());\n    builder.greedyMatchMethod(properties.isGreedyMatchMethod());\n    if (methodAccessValidator != null) {\n      builder.methodAccessValidator(methodAccessValidator);\n    }\n    return builder.build();\n  }\n}\n\nclass PebbleTemplatesHints implements RuntimeHintsRegistrar {\n\n  @Override\n  public void registerHints(RuntimeHints hints, ClassLoader classLoader) {\n    hints.reflection()\n        .registerType(TypeReference.of(UnaryMinusExpression.class),\n            hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))\n        .registerType(TypeReference.of(ForNode.LoopVariables.class),\n            hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS,\n                MemberCategory.DECLARED_CLASSES, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,\n                MemberCategory.INVOKE_DECLARED_METHODS));\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleProperties.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.http.MediaType;\nimport org.springframework.util.MimeType;\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\n@ConfigurationProperties(\"pebble\")\npublic class PebbleProperties {\n\n    private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf(\"text/html\");\n    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;\n\n    public static final String DEFAULT_PREFIX = \"/templates/\";\n    public static final String DEFAULT_SUFFIX = \".peb\";\n\n    private Locale defaultLocale;\n    private boolean strictVariables;\n    private boolean greedyMatchMethod;\n\n    private final Servlet servlet = new Servlet(this::getCharset);\n    private final Reactive reactive = new Reactive();\n\n    /**\n     * View names that can be resolved.\n     */\n    private String @Nullable [] viewNames;\n\n    /**\n     * Name of the RequestContext attribute for all views.\n     */\n    private @Nullable String requestContextAttribute;\n\n    /**\n     * Template encoding.\n     */\n    private Charset charset = DEFAULT_CHARSET;\n\n    /**\n     * Whether to check that the templates location exists.\n     */\n    private boolean checkTemplateLocation = true;\n\n    /**\n     * Prefix to apply to template names.\n     */\n    private String prefix = DEFAULT_PREFIX;\n\n    /**\n     * Suffix to apply to template names.\n     */\n    private String suffix = DEFAULT_SUFFIX;\n\n    public Servlet getServlet() {\n        return this.servlet;\n    }\n\n    public Reactive getReactive() {\n        return this.reactive;\n    }\n\n    public String getPrefix() {\n        return this.prefix;\n    }\n\n    public void setPrefix(String prefix) {\n        this.prefix = prefix;\n    }\n\n    public String getSuffix() {\n        return this.suffix;\n    }\n\n    public void setSuffix(String suffix) {\n        this.suffix = suffix;\n    }\n\n    public String @Nullable [] getViewNames() {\n        return this.viewNames;\n    }\n\n    public void setViewNames(String @Nullable [] viewNames) {\n        this.viewNames = viewNames;\n    }\n\n    public @Nullable String getRequestContextAttribute() {\n        return this.requestContextAttribute;\n    }\n\n    public void setRequestContextAttribute(@Nullable String requestContextAttribute) {\n        this.requestContextAttribute = requestContextAttribute;\n    }\n\n    public Charset getCharset() {\n        return this.charset;\n    }\n\n    public String getCharsetName() {\n        return this.charset.name();\n    }\n\n    public void setCharset(Charset charset) {\n        this.charset = charset;\n    }\n\n    public boolean isCheckTemplateLocation() {\n        return this.checkTemplateLocation;\n    }\n\n    public void setCheckTemplateLocation(boolean checkTemplateLocation) {\n        this.checkTemplateLocation = checkTemplateLocation;\n    }\n\n    public static class Servlet {\n\n        /**\n         * Whether HttpServletRequest attributes are allowed to override (hide) controller\n         * generated model attributes of the same name.\n         */\n        private boolean allowRequestOverride = false;\n\n        /**\n         * Whether HttpSession attributes are allowed to override (hide) controller\n         * generated model attributes of the same name.\n         */\n        private boolean allowSessionOverride = false;\n\n        /**\n         * Whether to enable template caching.\n         */\n        private boolean cache = true;\n\n        /**\n         * Content-Type value.\n         */\n        private MimeType contentType = DEFAULT_CONTENT_TYPE;\n\n        /**\n         * Whether all request attributes should be added to the model prior to merging\n         * with the template.\n         */\n        private boolean exposeRequestAttributes = false;\n\n        /**\n         * Whether all HttpSession attributes should be added to the model prior to\n         * merging with the template.\n         */\n        private boolean exposeSessionAttributes = false;\n\n        /**\n         * Whether to expose a RequestContext for use by Spring's macro library, under the\n         * name \"springMacroRequestContext\".\n         */\n        private boolean exposeSpringMacroHelpers = true;\n\n        private final Supplier<@Nullable Charset> charset;\n\n        public Servlet() {\n            this.charset = () -> null;\n        }\n\n        private Servlet(Supplier<@Nullable Charset> charset) {\n            this.charset = charset;\n        }\n\n        public boolean isAllowRequestOverride() {\n            return this.allowRequestOverride;\n        }\n\n        public void setAllowRequestOverride(boolean allowRequestOverride) {\n            this.allowRequestOverride = allowRequestOverride;\n        }\n\n        public boolean isAllowSessionOverride() {\n            return this.allowSessionOverride;\n        }\n\n        public void setAllowSessionOverride(boolean allowSessionOverride) {\n            this.allowSessionOverride = allowSessionOverride;\n        }\n\n        public boolean isCache() {\n            return this.cache;\n        }\n\n        public void setCache(boolean cache) {\n            this.cache = cache;\n        }\n\n        public MimeType getContentType() {\n            if (this.contentType != null && this.contentType.getCharset() == null) {\n                Charset charset = this.charset.get();\n                if (charset != null) {\n                    Map<String, String> parameters = new LinkedHashMap<>();\n                    parameters.put(\"charset\", charset.name());\n                    parameters.putAll(this.contentType.getParameters());\n                    return new MimeType(this.contentType, parameters);\n                }\n            }\n            return this.contentType;\n        }\n\n        public void setContentType(MimeType contentType) {\n            this.contentType = contentType;\n        }\n\n        public boolean isExposeRequestAttributes() {\n            return this.exposeRequestAttributes;\n        }\n\n        public void setExposeRequestAttributes(boolean exposeRequestAttributes) {\n            this.exposeRequestAttributes = exposeRequestAttributes;\n        }\n\n        public boolean isExposeSessionAttributes() {\n            return this.exposeSessionAttributes;\n        }\n\n        public void setExposeSessionAttributes(boolean exposeSessionAttributes) {\n            this.exposeSessionAttributes = exposeSessionAttributes;\n        }\n\n        public boolean isExposeSpringMacroHelpers() {\n            return this.exposeSpringMacroHelpers;\n        }\n\n        public void setExposeSpringMacroHelpers(boolean exposeSpringMacroHelpers) {\n            this.exposeSpringMacroHelpers = exposeSpringMacroHelpers;\n        }\n\n    }\n\n    public static class Reactive {\n\n        /**\n         * Media types supported by Mustache views.\n         */\n        private @Nullable List<MediaType> mediaTypes;\n\n        public @Nullable List<MediaType> getMediaTypes() {\n            return this.mediaTypes;\n        }\n\n        public void setMediaTypes(@Nullable List<MediaType> mediaTypes) {\n            this.mediaTypes = mediaTypes;\n        }\n\n    }\n\n    public Locale getDefaultLocale() {\n        return this.defaultLocale;\n    }\n\n    public void setDefaultLocale(Locale defaultLocale) {\n        this.defaultLocale = defaultLocale;\n    }\n\n    public boolean isStrictVariables() {\n        return this.strictVariables;\n    }\n\n    public void setStrictVariables(boolean strictVariables) {\n        this.strictVariables = strictVariables;\n    }\n\n    public boolean isGreedyMatchMethod() {\n        return this.greedyMatchMethod;\n    }\n\n    public void setGreedyMatchMethod(boolean greedyMatchMethod) {\n        this.greedyMatchMethod = greedyMatchMethod;\n    }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleReactiveWebConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.spring.reactive.PebbleReactiveViewResolver;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;\nimport org.springframework.boot.context.properties.PropertyMapper;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication(type = Type.REACTIVE)\nclass PebbleReactiveWebConfiguration extends AbstractPebbleConfiguration {\n\n    @Bean\n    @ConditionalOnMissingBean(name = \"pebbleReactiveViewResolver\")\n    PebbleReactiveViewResolver pebbleReactiveViewResolver(PebbleProperties properties,\n                                                          PebbleEngine pebbleEngine) {\n        PebbleReactiveViewResolver resolver = new PebbleReactiveViewResolver(pebbleEngine);\n        PropertyMapper map = PropertyMapper.get();\n        map.from(() -> {\n            String prefix = properties.getPrefix();\n            if (pebbleEngine.getLoader() instanceof ClasspathLoader) {\n                // classpathloader doesn't like leading slashes in paths\n                prefix = this.stripLeadingSlash(properties.getPrefix());\n            }\n            return prefix;\n        }).to(resolver::setPrefix);\n        map.from(properties::getSuffix).to(resolver::setSuffix);\n        map.from(properties::getViewNames).to(resolver::setViewNames);\n        map.from(properties::getRequestContextAttribute).to(resolver::setRequestContextAttribute);\n        map.from(properties.getReactive()::getMediaTypes).to(resolver::setSupportedMediaTypes);\n        resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);\n\n        return resolver;\n    }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleServletWebConfiguration.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.spring.servlet.PebbleViewResolver;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication(type = Type.SERVLET)\nclass PebbleServletWebConfiguration extends AbstractPebbleConfiguration {\n\n    @Bean\n    @ConditionalOnMissingBean(name = \"pebbleViewResolver\")\n    PebbleViewResolver pebbleViewResolver(PebbleProperties properties,\n                                          PebbleEngine pebbleEngine) {\n        PebbleViewResolver pvr = new PebbleViewResolver(pebbleEngine);\n\n        String prefix = properties.getPrefix();\n        if (pebbleEngine.getLoader() instanceof ClasspathLoader) {\n            // classpathloader doesn't like leading slashes in paths\n            prefix = this.stripLeadingSlash(properties.getPrefix());\n        }\n        pvr.setPrefix(prefix);\n        pvr.setSuffix(properties.getSuffix());\n        pvr.setCache(properties.getServlet().isCache());\n        if (properties.getServlet().getContentType() != null) {\n            pvr.setContentType(properties.getServlet().getContentType().toString());\n        }\n        pvr.setViewNames(properties.getViewNames());\n        pvr.setExposeRequestAttributes(properties.getServlet().isExposeRequestAttributes());\n        pvr.setAllowRequestOverride(properties.getServlet().isAllowRequestOverride());\n        pvr.setAllowSessionOverride(properties.getServlet().isAllowSessionOverride());\n        pvr.setExposeSessionAttributes(properties.getServlet().isExposeSessionAttributes());\n        pvr.setExposeSpringMacroHelpers(properties.getServlet().isExposeSpringMacroHelpers());\n        pvr.setRequestContextAttribute(properties.getRequestContextAttribute());\n        pvr.setCharacterEncoding(properties.getCharsetName());\n        pvr.setOrder(Ordered.LOWEST_PRECEDENCE - 10);\n\n        return pvr;\n    }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/PebbleTemplateAvailabilityProvider.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;\nimport org.springframework.core.env.Environment;\nimport org.springframework.core.io.ResourceLoader;\nimport org.springframework.util.ClassUtils;\n\nimport static org.springframework.core.io.ResourceLoader.CLASSPATH_URL_PREFIX;\n\npublic class PebbleTemplateAvailabilityProvider implements TemplateAvailabilityProvider {\n\n  @Override\n  public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,\n      ResourceLoader resourceLoader) {\n    if (ClassUtils.isPresent(PebbleEngine.class.getCanonicalName(), classLoader)) {\n      String prefix = environment.getProperty(\"pebble.prefix\", PebbleProperties.DEFAULT_PREFIX);\n      String suffix = environment.getProperty(\"pebble.suffix\", PebbleProperties.DEFAULT_SUFFIX);\n      return resourceLoader.getResource(CLASSPATH_URL_PREFIX + prefix + view + suffix).exists();\n    }\n    return false;\n  }\n\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/java/io/pebbletemplates/boot/autoconfigure/package-info.java",
    "content": "/**\n * Auto-configuration for Pebble Template Engine.\n */\npackage io.pebbletemplates.boot.autoconfigure;"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/resources/META-INF/spring/aot.factories",
    "content": "org.springframework.aot.hint.RuntimeHintsRegistrar=\\\nio.pebbletemplates.boot.autoconfigure.PebbleTemplatesHints"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "io.pebbletemplates.boot.autoconfigure.PebbleAutoConfiguration"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.webflux.test.autoconfigure.AutoConfigureWebFlux.imports",
    "content": "io.pebbletemplates.boot.autoconfigure.PebbleAutoConfiguration"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports",
    "content": "io.pebbletemplates.boot.autoconfigure.PebbleAutoConfiguration"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/main/resources/META-INF/spring.factories",
    "content": "# Template availability providers\norg.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\\\nio.pebbletemplates.boot.autoconfigure.PebbleTemplateAvailabilityProvider"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/AppConfig.java",
    "content": "package io.pebbletemplates.boot;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Extension;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\n\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.support.ResourceBundleMessageSource;\nimport org.springframework.web.servlet.LocaleResolver;\nimport org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n@Configuration(proxyBeanMethods = false)\npublic class AppConfig {\n  @Bean\n  public MessageSource messageSource() {\n    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();\n    messageSource.setBasename(\"messages\");\n    messageSource.setFallbackToSystemLocale(false);\n    return messageSource;\n  }\n\n  @Bean\n  public LocaleResolver localeResolver() {\n    return new AcceptHeaderLocaleResolver();\n  }\n\n  @Bean\n  public Extension testExtension() {\n    return new TestExtension();\n  }\n\n  public static class TestExtension extends AbstractExtension {\n\n    @Override\n    public Map<String, Function> getFunctions() {\n      Map<String, Function> functions = new HashMap<String, Function>();\n      functions.put(\"testFunction\", new TestFunction());\n      return functions;\n    }\n\n    public static class TestFunction implements Function {\n\n      @Override\n      public List<String> getArgumentNames() {\n        return Collections.emptyList();\n      }\n\n      @Override\n      public Object execute(Map<String, Object> args, PebbleTemplate self,\n                            EvaluationContext context, int lineNumber) {\n        return \"Tested!\";\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/Application.java",
    "content": "package io.pebbletemplates.boot;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n  public static void main(String[] args) {\n    SpringApplication.run(Application.class, args);\n  }\n}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/Controllers.java",
    "content": "package io.pebbletemplates.boot;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.server.WebSession;\n\n@Controller\npublic class Controllers {\n\n  @RequestMapping(\"/hello.action\")\n  public String hello() {\n    return \"hello\";\n  }\n\n  @RequestMapping(\"/index.action\")\n  public String index() {\n    return \"index\";\n  }\n\n  @RequestMapping(\"/contextPath.action\")\n  public String contextPath() {\n    return \"contextPath\";\n  }\n\n  @RequestMapping(\"/extensions.action\")\n  public String extensions() {\n    return \"extensions\";\n  }\n\n  @RequestMapping(\"/beans.action\")\n  public String beans() {\n    return \"beans\";\n  }\n\n  @RequestMapping(\"/session.action\")\n  public String session(WebSession session) {\n    session.getAttributes().put(\"foo\", \"bar\");\n    return \"session\";\n  }\n\n  @RequestMapping(\"/response.action\")\n  public String response() {\n    return \"responseObject\";\n  }\n\n}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/Foo.java",
    "content": "package io.pebbletemplates.boot;\n\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class Foo {\n\n  public String value = \"bar\";\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/NonWebAppTests.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.boot.Application;\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\n\nimport java.io.StringWriter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@SpringBootTest(classes = Application.class,\n        properties = \"spring.main.web-application-type=none\")\nclass NonWebAppTests {\n\n  @Autowired\n  private PebbleEngine pebbleEngine;\n\n  @Test\n  void testOk() throws Exception {\n    StringWriter sw = new StringWriter();\n    this.pebbleEngine.getTemplate(\"hello\").evaluate(sw);\n    assertThat(sw.toString() != null && !sw.toString().isEmpty()).isTrue();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/PebbleAutoConfigurationTest.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.attributes.methodaccess.BlacklistMethodAccessValidator;\nimport io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;\nimport io.pebbletemplates.pebble.attributes.methodaccess.NoOpMethodAccessValidator;\nimport io.pebbletemplates.pebble.cache.tag.NoOpTagCache;\nimport io.pebbletemplates.pebble.cache.template.NoOpTemplateCache;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.spring.extension.SpringExtension;\nimport io.pebbletemplates.spring.reactive.PebbleReactiveView;\nimport io.pebbletemplates.spring.reactive.PebbleReactiveViewResolver;\nimport io.pebbletemplates.spring.servlet.PebbleView;\nimport io.pebbletemplates.spring.servlet.PebbleViewResolver;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.util.TestPropertyValues;\nimport org.springframework.boot.web.context.reactive.AnnotationConfigReactiveWebApplicationContext;\nimport org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.web.reactive.result.view.UrlBasedViewResolver;\nimport org.springframework.web.servlet.view.AbstractTemplateViewResolver;\n\nimport java.util.Locale;\n\nimport static java.util.Locale.CHINESE;\nimport static java.util.Locale.FRENCH;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PebbleAutoConfigurationTest {\n\n  private static final Locale DEFAULT_LOCALE = CHINESE;\n  private static final Locale CUSTOM_LOCALE = FRENCH;\n  private AnnotationConfigServletWebApplicationContext webContext;\n  private AnnotationConfigReactiveWebApplicationContext reactiveWebContext;\n\n  @Test\n  void registerBeansForServletApp() {\n    this.loadWithServlet(null);\n    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()).isTrue();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().getMethodAccessValidator()).isInstanceOf(BlacklistMethodAccessValidator.class);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getTagCache()).isInstanceOf(NoOpTagCache.class);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getTemplateCache()).isInstanceOf(NoOpTemplateCache.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);\n  }\n\n  @Test\n  void registerCustomBeansForServletApp() {\n    this.loadWithServlet(CustomPebbleViewResolverConfiguration.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(0);\n    assertThat(this.webContext.getBeansOfType(AbstractTemplateViewResolver.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(CustomPebbleViewResolver.class)).hasSize(1);\n  }\n\n  @Test\n  void registerCompilerForServletApp() {\n    this.loadWithServlet(CustomPebbleEngineCompilerConfiguration.class);\n    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale())\n            .isEqualTo(CUSTOM_LOCALE);\n    assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isFalse();\n    assertThat(\n            this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod())\n            .isFalse();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .getMethodAccessValidator()).isInstanceOf(\n            BlacklistMethodAccessValidator.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerCustomMethodAccessValidatorForServletApp() {\n    this.loadWithServlet(CustomMethodAccessValidatorConfiguration.class);\n    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale())\n            .isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(\n            this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod())\n            .isTrue();\n    assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .getMethodAccessValidator()).isInstanceOf(\n            NoOpMethodAccessValidator.class);\n    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);\n    assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerBeansForReactiveApp() {\n    this.loadWithReactive(null);\n    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale())\n            .isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .isGreedyMatchMethod()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .getMethodAccessValidator()).isInstanceOf(\n            BlacklistMethodAccessValidator.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerCustomBeansForReactiveApp() {\n    this.loadWithReactive(CustomPebbleReactiveViewResolverConfiguration.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(0);\n    assertThat(this.reactiveWebContext.getBeansOfType(UrlBasedViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(CustomPebbleReactiveViewResolver.class)).hasSize(1);\n  }\n\n  @Test\n  void registerCompilerForReactiveApp() {\n    this.loadWithReactive(CustomPebbleEngineCompilerConfiguration.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale())\n            .isEqualTo(CUSTOM_LOCALE);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isFalse();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .isGreedyMatchMethod()).isFalse();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .getMethodAccessValidator()).isInstanceOf(\n            BlacklistMethodAccessValidator.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n  }\n\n  @Test\n  void registerCustomMethodAccessValidatorForReactiveApp() {\n    this.loadWithReactive(CustomMethodAccessValidatorConfiguration.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale())\n            .isEqualTo(DEFAULT_LOCALE);\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .isGreedyMatchMethod()).isTrue();\n    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions()\n            .getMethodAccessValidator()).isInstanceOf(\n            NoOpMethodAccessValidator.class);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);\n    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();\n  }\n\n  private void loadWithServlet(Class<?> config) {\n    this.webContext = new AnnotationConfigServletWebApplicationContext();\n    TestPropertyValues.of(\"pebble.prefix=classpath:/templates/\").applyTo(this.webContext);\n    TestPropertyValues.of(\"pebble.defaultLocale=zh\").applyTo(this.webContext);\n    TestPropertyValues.of(\"pebble.strictVariables=true\").applyTo(this.webContext);\n    TestPropertyValues.of(\"pebble.greedyMatchMethod=true\").applyTo(this.webContext);\n    TestPropertyValues.of(\"pebble.servlet.cache=false\").applyTo(this.webContext);\n    if (config != null) {\n      this.webContext.register(config);\n    }\n    this.webContext.register(BaseConfiguration.class);\n    this.webContext.refresh();\n  }\n\n  private void loadWithReactive(Class<?> config) {\n    this.reactiveWebContext = new AnnotationConfigReactiveWebApplicationContext();\n    TestPropertyValues.of(\"pebble.prefix=classpath:/templates/\").applyTo(this.reactiveWebContext);\n    TestPropertyValues.of(\"pebble.defaultLocale=zh\").applyTo(this.reactiveWebContext);\n    TestPropertyValues.of(\"pebble.strictVariables=true\").applyTo(this.reactiveWebContext);\n    TestPropertyValues.of(\"pebble.greedyMatchMethod=true\").applyTo(this.reactiveWebContext);\n    if (config != null) {\n      this.reactiveWebContext.register(config);\n    }\n    this.reactiveWebContext.register(BaseConfiguration.class);\n    this.reactiveWebContext.refresh();\n  }\n\n  @Configuration(proxyBeanMethods = false)\n  @Import(PebbleAutoConfiguration.class)\n  protected static class BaseConfiguration {\n\n  }\n\n  @Configuration(proxyBeanMethods = false)\n  protected static class CustomPebbleEngineCompilerConfiguration {\n\n    @Bean\n    public PebbleEngine pebbleEngine() {\n      return new PebbleEngine.Builder().defaultLocale(CUSTOM_LOCALE).build();\n    }\n\n    @Bean\n    public SpringExtension customSpringExtension(MessageSource messageSource) {\n      return new SpringExtension(messageSource);\n    }\n\n  }\n\n  @Configuration(proxyBeanMethods = false)\n  protected static class CustomPebbleViewResolverConfiguration {\n\n    @Bean\n    public CustomPebbleViewResolver pebbleViewResolver() {\n      return new CustomPebbleViewResolver();\n    }\n\n  }\n\n  @Configuration(proxyBeanMethods = false)\n  protected static class CustomPebbleReactiveViewResolverConfiguration {\n\n    @Bean\n    public CustomPebbleReactiveViewResolver pebbleReactiveViewResolver() {\n      return new CustomPebbleReactiveViewResolver();\n    }\n\n  }\n\n  protected static class CustomPebbleViewResolver extends AbstractTemplateViewResolver {\n\n    public CustomPebbleViewResolver() {\n      this.setViewClass(PebbleView.class);\n    }\n\n  }\n\n  protected static class CustomPebbleReactiveViewResolver extends UrlBasedViewResolver {\n\n    public CustomPebbleReactiveViewResolver() {\n      this.setViewClass(PebbleReactiveView.class);\n    }\n\n  }\n\n  @Configuration(proxyBeanMethods = false)\n  protected static class CustomMethodAccessValidatorConfiguration {\n\n    @Bean\n    public MethodAccessValidator methodAccessValidator() {\n      return new NoOpMethodAccessValidator();\n    }\n  }\n\n}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/ReactiveAppTest.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.boot.Application;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.webtestclient.autoconfigure.AutoConfigureWebTestClient;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.reactive.server.WebTestClient;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@SpringBootTest(classes = Application.class,\n        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,\n        properties = \"spring.main.web-application-type=reactive\")\n@AutoConfigureWebTestClient\nclass ReactiveAppTest {\n\n  @Autowired\n  private WebTestClient client;\n\n  @Test\n  void testOk() throws Exception {\n    String result = this.client.get().uri(\"/index.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hello Pebbleworld!\");\n  }\n\n  @Test\n  void testRequestAccess() throws Exception {\n    String result = this.client.get().uri(\"/contextPath.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"ctx path:/contextPath.action\");\n  }\n\n  @Test\n  void testEnglishHello() throws Exception {\n    String result = this.client.get().uri(\"/hello.action\")\n            .header(\"Accept-Language\", \"en\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hello Boot!\");\n  }\n\n  @Test\n  void testSpanishHello() throws Exception {\n    String result = this.client.get().uri(\"/hello.action\")\n            .header(\"Accept-Language\", \"es\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hola Boot!\");\n  }\n\n  @Test\n  void testAdditionalExtensions() throws Exception {\n    String result = this.client.get().uri(\"/extensions.action\")\n            .header(\"Accept-Language\", \"es\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"Hola Boot! Tested!\");\n  }\n\n  @Test\n  void testBeansAccess() throws Exception {\n    String result = this.client.get().uri(\"/beans.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"beans:bar\");\n  }\n\n  @Test\n  void testResponseAccess() throws Exception {\n    String result = this.client.get().uri(\"/response.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"response:200 OK\");\n  }\n\n  @Test\n  void testSessionAccess() {\n    String result = this.client.get().uri(\"/session.action\").exchange()\n            .expectStatus().isOk()\n            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n            .expectBody(String.class)\n            .returnResult().getResponseBody();\n\n    assertThat(result).isEqualTo(\"session:bar\");\n  }\n}\n\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/java/io/pebbletemplates/boot/autoconfigure/ServletAppTest.java",
    "content": "package io.pebbletemplates.boot.autoconfigure;\n\nimport io.pebbletemplates.boot.Application;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.MvcResult;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\nimport org.springframework.web.context.WebApplicationContext;\n\nimport java.util.Locale;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)\nclass ServletAppTest {\n\n  @Autowired\n  private WebApplicationContext wac;\n\n  protected MockMvc mockMvc;\n\n  @BeforeEach\n  void setup() {\n    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();\n  }\n\n  @Test\n  void testOk() throws Exception {\n    this.mockMvc.perform(get(\"/index.action\")).andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hello Pebbleworld!\"));\n  }\n\n  @Test\n  void testRequestAccess() throws Exception {\n    MvcResult result = this.mockMvc.perform(get(\"/contextPath.action\")).andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)).andReturn();\n\n    assertThat(result.getResponse().getContentAsString()).isEqualTo(\"ctx path:\" + result.getRequest().getContextPath());\n  }\n\n  @Test\n  void testEnglishHello() throws Exception {\n    this.mockMvc.perform(get(\"/hello.action\").locale(Locale.forLanguageTag(\"en\")))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hello Boot!\"));\n  }\n\n  @Test\n  void testSpanishHello() throws Exception {\n    this.mockMvc.perform(get(\"/hello.action\").locale(Locale.forLanguageTag(\"es\")))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hola Boot!\"));\n  }\n\n  @Test\n  void testAdditionalExtensions() throws Exception {\n    this.mockMvc.perform(get(\"/extensions.action\").locale(Locale.forLanguageTag(\"es\")))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"Hola Boot! Tested!\"));\n  }\n\n  @Test\n  void testBeansAccess() throws Exception {\n    this.mockMvc.perform(get(\"/beans.action\"))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"beans:bar\"));\n  }\n\n  @Test\n  void testResponseAccess() throws Exception {\n    this.mockMvc.perform(get(\"/response.action\"))\n            .andExpect(status().isOk())\n            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))\n            .andExpect(content().string(\"response:200\"));\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/messages.properties",
    "content": "hello.boot=Hello Boot!"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/messages_es.properties",
    "content": "hello.boot=Hola Boot!"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/beans.peb",
    "content": "beans:{{beans.foo.value}}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/contextPath.peb",
    "content": "ctx path:{{request.contextPath}}{{request.path}}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/extensions.peb",
    "content": "{{ message('hello.boot') }} {{ testFunction() }}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/hello.peb",
    "content": "{{ message('hello.boot') }}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/index.peb",
    "content": "{{'Hello Pebbleworld!'}}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/native-image.peb",
    "content": "{{ foo.bar }}\n\n{{ foo[\"bar\"] }}\n\n{{ bar[0] }}\n\n{{ \"If life gives you lemons, eat lemons.\" | upper | abbreviate(13) }}\n\n{{ max(13, highscore) }}\n\n{% for article in articles %}\n    <p>{{ article }}</p>\n{% else %}\n    <p> There are no articles. </p>\n{% endfor %}\n\n{% if category == \"news\" %}\n{{ news }}\n    {% elseif category == \"sports\" %}\n{{ sports }}\n    {% else %}\n    <p>Please select a category</p>\n{% endif %}\n\n{{ stringDate | date(existingFormat=\"yyyy-MMMM-d\", format=\"yyyy/MMMM/d\") }}\n\n{{ stringDate | date(\"yyyy/MMMM/d\", existingFormat=\"yyyy-MMMM-d\") }}\n\n{% set danger = \"<br>\" %}\n{{ danger }}\n\n{% set danger = \"<br>\" %}\n{{ danger | escape }}\n\n{# THIS IS A COMMENT #}\n{% if 3 is odd %}\n\t...\n{% endif %}\n\n{% for article in articles %}\n    <p>number: ; content: {{ article }}</p>\n{% else %}\n    <p> There are no articles. </p>\n{% endfor %}\n\n{% if name is not null %}\n\t...\n{% endif %}\n\n{{ folk ? \"yes\" : \"no\" }}\n\n{{ user.name | capitalize }}\n\n{% if user.name equals \"Mitchell\" %}\n\tDavid\n{% endif %}\n\n    {% if bar contains 2 %}\n        Two\n    {% endif %}\n\n{% if (3 is not even) and (2 is odd or 3 is even) %}\n\t...\n{% endif %}\n\n{% if user.telephone is empty %}\n\t...\n{% endif %}\n\n{% if user is not map %}\n\t...\n{% endif %}\n\n{% if user.email is null %}\n\tuser.email is null\n{% endif %}\n\n{% if users is iterable %}\n    {% for user in users %}\n\t\tUser: {{ user }}\n\t{% endfor %}\n{% endif %}\n\n{{ i18n(\"messages\",\"greetings\") }}\n\n{% for i in range(0, 36, 2) %}\n{{ i }},\n{% endfor %}\n\n{{ \"this is a long sentence.\" | abbreviate(7) }}\n\n{{ -7 | abs }}\n\n{{ \"dGVzdA==\" | base64decode }}\n\n{{ \"test\" | base64encode }}\n\n{{ \"article title\" | capitalize }}\n\n{{ \"July 24, 2001\" | date(\"yyyy-MM-dd\", existingFormat=\"MMMM dd, yyyy\") }}\n\n{{ user.telephone | default(\"No phone number\") }}\n\n{{ \"<div>\" | escape }}\n\n{{ users | first }}\n{# will output the first item in the collection named 'users' #}\n\n{{ 'Mitch' | first }}\n{# will output 'M' #}\n\n{{ names | join(',') }}\n{# will output: Alex,Joe,Bob #}\n\n{{ users | last }}\n{# will output the last item in the collection named 'users' #}\n\n{{ 'Mitch' | last }}\n{# will output 'h' #}\n\n{% if users|length > 10 %}\n    ...\n{% endif %}\n\n{{ \"THIS IS A LOUD SENTENCE\" | lower }}\n\n{{ 3.141592653 | numberformat(\"#.##\") }}\n\n{% set danger = \"<div>\" %}\n{{ danger | upper | raw }}\n{# ouptut: <DIV> #}\n\n {% set danger = \"<div>\" %}\n{{ danger | raw | upper }}\n{# output: &lt;DIV&gt; #}\n\n{{ \"I like %this% and %that%.\" | replace({'%this%': foo, '%that%': \"bar\"}) }}\n\n{% for user in users | reverse %} {{ user }} {% endfor %}\n\n{% for user in users | rsort %}\n    {{ user }}\n{% endfor %}\n\n{{ \"test\" | sha256 }}\n\n{{ ['apple', 'peach', 'pear', 'banana'] | slice(1,3) }}\n{# results in: [peach, pear] #}\n\n{{ 'Mitchell' | slice(1,3) }}\n{# results in: 'it' #}\n\n{% for user in users | sort %}\n    {{ user }}\n{% endfor %}\n\n{% set foo = \"one,two,three\" | split(',') %}\n{# foo contains ['one', 'two', 'three'] #}\n\n{% set foo = \"one,two,three,four,five\" | split(',', 3) %}\n{# foo contains ['one', 'two', 'three,four,five'] #}\n\n{{ \"article title\" | title }}\n\n{{ \"    This text has too much whitespace.    \" | trim }}\n\n{{ \"this is a quiet sentence.\" | upper }}\n\n{{ \"The string ü@foo-bar\" | urlencode }}\n\n{% block \"post\" %} content {% endblock %}\n\n{{ block(\"post\") }}\n\n{{ block(\"post\") }}\n\n\n{{ \"this is a long sentence.\" | abbreviate(7) }}\n\n{{ danger }} {# will be escaped by default #}\n{% autoescape false %}\n{{ danger }} {# will not be escaped #}\n{% endautoescape %}\n\n{{ danger }} {# will use the \"html\" escaping strategy #}\n{% autoescape \"js\" %}\n{{ danger }} {# will use the \"js\" escaping strategy #}\n{% endautoescape %}\n\n{% cache 'menu' %}\n    {% for item in items %}\n    {{ item.text }}\n        ....\n    {% endfor %}\n{% endcache %}\n\n{% filter upper %}\n\thello\n{% endfilter %}\n\n{% filter upper | escape %}\n\thello<br>\n{% endfilter %}\n\n{# output: 'HELLO&lt;br&gt;' #}\n\n{{ headerText }}\n    {% flush %}\n{{ content }}\n\n{% for user in users %}\n    {{ loop.index }}\n    {{ loop.length }}\n    {{ loop.first }}\n    {{ loop.last }}\n    {{ loop.revindex }}\n{% endfor %}\n\n{% include \"advertisement.peb\" with {\"foo\": \"bar\"} %}\n\n{% macro input(type=\"text\", name, value) %}\n\t<input type=\"{{ type }}\" name=\"{{ name }}\" value=\"{{ value }}\" />\n{% endmacro %}\n\n{{ input(name=\"country\") }}\n{# will output: <input type=\"text\" name=\"country\" value=\"\" /> #}\n\n{% verbatim %}\n\t{% for user in users %}\n\t\t{{ user.name }}\n\t{% endfor %}\n{% endverbatim %}\n"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/responseObject.peb",
    "content": "response:{{response.status}}{{response.statusCode}}"
  },
  {
    "path": "pebble-spring/pebble-spring-boot-starter/src/test/resources/templates/session.peb",
    "content": "session:{{ session.attributes.foo }}"
  },
  {
    "path": "pebble-spring/pebble-spring6/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <artifactId>pebble-spring</artifactId>\n    <groupId>io.pebbletemplates</groupId>\n    <version>4.1.2-SNAPSHOT</version>\n  </parent>\n\n  <artifactId>pebble-spring6</artifactId>\n\n  <name>Pebble Integration with Spring 6.x</name>\n  <description>Pebble Integration with Spring 6.x</description>\n  <url>http://pebbletemplates.io</url>\n\n  <properties>\n    <java.version>17</java.version>\n    <servlet-api.version>6.0.0</servlet-api.version>\n    <spring-framework.version>6.2.18</spring-framework.version>\n    <mockito-junit-jupiter.version>5.23.0</mockito-junit-jupiter.version>\n    <junit-jupiter.version>5.14.4</junit-jupiter.version>\n  </properties>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>org.springframework</groupId>\n        <artifactId>spring-framework-bom</artifactId>\n        <version>${spring-framework.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <dependencies>\n    <dependency>\n      <groupId>io.pebbletemplates</groupId>\n      <artifactId>pebble</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.servlet</groupId>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <version>${servlet-api.version}</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-webmvc</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-webflux</artifactId>\n      <scope>provided</scope>\n    </dependency>\n\n    <!-- TEST -->\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-test</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter</artifactId>\n      <version>${junit-jupiter.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-junit-jupiter</artifactId>\n      <version>${mockito-junit-jupiter.version}</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifestEntries>\n              <Automatic-Module-Name>io.pebbletemplates.spring</Automatic-Module-Name>\n            </manifestEntries>\n          </archive>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/context/Beans.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.context;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.util.Assert;\n\n/**\n * <p>\n * Special object made available to templates in Spring MVC applications in order to access beans in\n * the Application Context.\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class Beans implements Map<String, Object> {\n\n  private final ApplicationContext ctx;\n\n  public Beans(ApplicationContext ctx) {\n    Assert.notNull(ctx, \"Application Context cannot be null\");\n    this.ctx = ctx;\n  }\n\n  @Override\n  public void clear() {\n    throw new UnsupportedOperationException(\"Method \\\"clear\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public boolean containsKey(Object key) {\n    Assert.notNull(key, \"Key cannot be null\");\n    return this.ctx.containsBean(key.toString());\n  }\n\n  @Override\n  public boolean containsValue(Object value) {\n    throw new UnsupportedOperationException(\n        \"Method \\\"containsValue\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public Set<Entry<String, Object>> entrySet() {\n    throw new UnsupportedOperationException(\"Method \\\"entrySet\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public Object get(Object key) {\n    Assert.notNull(key, \"Key cannot be null\");\n    return this.ctx.getBean(key.toString());\n  }\n\n  @Override\n  public boolean isEmpty() {\n    return this.size() <= 0;\n  }\n\n  @Override\n  public Set<String> keySet() {\n    return new LinkedHashSet<>(Arrays.asList(this.ctx.getBeanDefinitionNames()));\n  }\n\n  @Override\n  public Object put(String key, Object value) {\n    throw new UnsupportedOperationException(\"Method \\\"put\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public void putAll(Map<? extends String, ?> m) {\n    throw new UnsupportedOperationException(\"Method \\\"putAll\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public Object remove(Object key) {\n    throw new UnsupportedOperationException(\"Method \\\"remove\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public int size() {\n    return this.ctx.getBeanDefinitionCount();\n  }\n\n  @Override\n  public Collection<Object> values() {\n    throw new UnsupportedOperationException(\"Method \\\"values\\\" not supported in Beans object\");\n  }\n\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/SpringExtension.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.spring.extension.function.HrefFunction;\nimport io.pebbletemplates.spring.extension.function.MessageSourceFunction;\nimport io.pebbletemplates.spring.extension.function.bindingresult.GetAllErrorsFunction;\nimport io.pebbletemplates.spring.extension.function.bindingresult.GetFieldErrorsFunction;\nimport io.pebbletemplates.spring.extension.function.bindingresult.GetGlobalErrorsFunction;\nimport io.pebbletemplates.spring.extension.function.bindingresult.HasErrorsFunction;\nimport io.pebbletemplates.spring.extension.function.bindingresult.HasFieldErrorsFunction;\nimport io.pebbletemplates.spring.extension.function.bindingresult.HasGlobalErrorsFunction;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.context.MessageSource;\n\n/**\n * <p>\n * Extension for PebbleEngine to add spring functionality\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class SpringExtension extends AbstractExtension {\n\n  private final MessageSource messageSource;\n\n  public SpringExtension(MessageSource messageSource) {\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Map<String, Function> getFunctions() {\n    Map<String, Function> functions = new HashMap<>();\n    functions\n        .put(MessageSourceFunction.FUNCTION_NAME, new MessageSourceFunction(this.messageSource));\n    functions.put(HasErrorsFunction.FUNCTION_NAME, new HasErrorsFunction());\n    functions.put(HasGlobalErrorsFunction.FUNCTION_NAME, new HasGlobalErrorsFunction());\n    functions.put(HasFieldErrorsFunction.FUNCTION_NAME, new HasFieldErrorsFunction());\n    functions.put(GetAllErrorsFunction.FUNCTION_NAME, new GetAllErrorsFunction(this.messageSource));\n    functions.put(GetGlobalErrorsFunction.FUNCTION_NAME,\n        new GetGlobalErrorsFunction(this.messageSource));\n    functions\n        .put(GetFieldErrorsFunction.FUNCTION_NAME, new GetFieldErrorsFunction(this.messageSource));\n    functions.put(HrefFunction.FUNCTION_NAME, new HrefFunction());\n    return functions;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/HrefFunction.java",
    "content": "package io.pebbletemplates.spring.extension.function;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.extension.NamedArguments;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Pebble function which adds the context path to the given url\n *\n * @author Eric Bussieres\n */\npublic class HrefFunction implements Function {\n\n  public static final String FUNCTION_NAME = \"href\";\n\n  protected static final String PARAM_URL = \"url\";\n\n  protected List<String> argumentNames;\n  private String contextPath;\n\n  /**\n   * Constructor\n   */\n  public HrefFunction() {\n    this.argumentNames = new ArrayList<>();\n    this.argumentNames.add(PARAM_URL);\n  }\n\n  /**\n   * {@inheritDoc}\n   *\n   * @see Function#execute(Map, PebbleTemplate, EvaluationContext, int)\n   */\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    StringBuffer result = new StringBuffer();\n\n    result.append(this.getContextPath());\n    this.addUrlParameter(args, result);\n\n    return result.toString();\n  }\n\n  private void addUrlParameter(Map<String, Object> args, StringBuffer result) {\n    String url = (String) args.get(PARAM_URL);\n    if (StringUtils.hasText(url)) {\n      result.append(url);\n    }\n  }\n\n  private String getContextPath() {\n    if (this.contextPath == null) {\n      this.contextPath = this.getRequest().getContextPath();\n    }\n    return this.contextPath;\n  }\n\n  private HttpServletRequest getRequest() {\n    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();\n    return attr.getRequest();\n  }\n\n  /**\n   * {@inheritDoc}\n   *\n   * @see NamedArguments#getArgumentNames()\n   */\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/MessageSourceFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to resolve message in the\n * application context\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class MessageSourceFunction implements Function {\n\n  public static final String FUNCTION_NAME = \"message\";\n\n  private final MessageSource messageSource;\n\n  public MessageSourceFunction(MessageSource messageSource) {\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    String key = this.extractKey(args);\n    List<Object> arguments = this.extractArguments(args);\n    Locale locale = context.getLocale();\n\n    return this.messageSource.getMessage(key, arguments.toArray(), \"???\" + key + \"???\", locale);\n  }\n\n  private String extractKey(Map<String, Object> args) {\n    return (String) args.get(\"0\");\n  }\n\n  private List<Object> extractArguments(Map<String, Object> args) {\n    int i = 1;\n    List<Object> arguments = new ArrayList<>();\n    while (args.containsKey(String.valueOf(i))) {\n      Object param = args.get(String.valueOf(i));\n      arguments.add(param);\n      i++;\n    }\n    return arguments;\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/BaseBindingResultFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.GlobalContext;\n\nimport org.springframework.validation.BindingResult;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Base class of the function interacting with the BindingResult\n *\n * @author Eric Bussieres\n */\npublic abstract class BaseBindingResultFunction implements Function {\n\n  protected static final String PARAM_FIELD_NAME = \"fieldName\";\n  protected static final String PARAM_FORM_NAME = \"formName\";\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  protected BaseBindingResultFunction(String... argumentsName) {\n    Collections.addAll(this.argumentNames, argumentsName);\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  protected BindingResult getBindingResult(String formName, EvaluationContext context) {\n    String attribute = BindingResult.MODEL_KEY_PREFIX + formName;\n    BindingResult bindingResult = (BindingResult) context.getVariable(attribute);\n    if (bindingResult == null) {\n      GlobalContext globalContext = (GlobalContext) context.getVariable(\"_context\");\n      if (globalContext != null) {\n        return (BindingResult) globalContext.get(attribute);\n      }\n    }\n    return bindingResult;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/GetAllErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.ObjectError;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class GetAllErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"getAllErrors\";\n\n  private final MessageSource messageSource;\n\n  public GetAllErrorsFunction(MessageSource messageSource) {\n    super(PARAM_FORM_NAME);\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    Locale locale = context.getLocale();\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    return this.constructErrorMessage(locale, bindingResult);\n  }\n\n  private List<String> constructErrorMessage(Locale locale, BindingResult bindingResult) {\n    List<String> errorMessages = new ArrayList<>();\n    if (bindingResult != null) {\n      for (ObjectError error : bindingResult.getAllErrors()) {\n        String msg = this.messageSource.getMessage(error, locale);\n        errorMessages.add(msg);\n      }\n    }\n    return errorMessages;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/GetFieldErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class GetFieldErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"getFieldErrors\";\n\n  private final MessageSource messageSource;\n\n  public GetFieldErrorsFunction(MessageSource messageSource) {\n    super(PARAM_FORM_NAME, PARAM_FIELD_NAME);\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n    String field = (String) args.get(PARAM_FIELD_NAME);\n\n    if (field == null) {\n      throw new IllegalArgumentException(\"Field parameter is required in GetFieldErrorsFunction\");\n    }\n\n    Locale locale = context.getLocale();\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    return this.constructErrorMessages(field, locale, bindingResult);\n  }\n\n  private List<String> constructErrorMessages(String field, Locale locale,\n      BindingResult bindingResult) {\n    List<String> errorMessages = new ArrayList<>();\n    if (bindingResult != null) {\n      for (FieldError error : bindingResult.getFieldErrors(field)) {\n        String msg = this.messageSource.getMessage(error, locale);\n        errorMessages.add(msg);\n      }\n    }\n    return errorMessages;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/GetGlobalErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.ObjectError;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class GetGlobalErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"getGlobalErrors\";\n\n  private final MessageSource messageSource;\n\n  public GetGlobalErrorsFunction(MessageSource messageSource) {\n    super(PARAM_FORM_NAME);\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    Locale locale = context.getLocale();\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    return this.constructErrorMessages(locale, bindingResult);\n  }\n\n  private List<String> constructErrorMessages(Locale locale, BindingResult bindingResult) {\n    List<String> errorMessages = new ArrayList<>();\n    if (bindingResult != null) {\n      for (ObjectError error : bindingResult.getGlobalErrors()) {\n        String msg = this.messageSource.getMessage(error, locale);\n        errorMessages.add(msg);\n      }\n    }\n    return errorMessages;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/HasErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.Map;\nimport org.springframework.validation.BindingResult;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class HasErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"hasErrors\";\n\n  public HasErrorsFunction() {\n    super(PARAM_FORM_NAME);\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n    return bindingResult != null && bindingResult.hasErrors();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/HasFieldErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.Map;\nimport org.springframework.validation.BindingResult;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class HasFieldErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"hasFieldErrors\";\n\n  public HasFieldErrorsFunction() {\n    super(PARAM_FORM_NAME, PARAM_FIELD_NAME);\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n    String fieldName = (String) args.get(PARAM_FIELD_NAME);\n\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    if (bindingResult != null) {\n      if (fieldName == null) {\n        return bindingResult.hasFieldErrors();\n      } else {\n        return bindingResult.hasFieldErrors(fieldName);\n      }\n    } else {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/HasGlobalErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.Map;\nimport org.springframework.validation.BindingResult;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class HasGlobalErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"hasGlobalErrors\";\n\n  public HasGlobalErrorsFunction() {\n    super(PARAM_FORM_NAME);\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n    return bindingResult != null && bindingResult.hasGlobalErrors();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/reactive/PebbleReactiveView.java",
    "content": "package io.pebbletemplates.spring.reactive;\n\nimport static java.util.Optional.ofNullable;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.spring.context.Beans;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.nio.charset.Charset;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport org.springframework.context.i18n.LocaleContextHolder;\nimport org.springframework.core.io.buffer.DataBuffer;\nimport org.springframework.core.io.buffer.DataBufferUtils;\nimport org.springframework.http.MediaType;\nimport org.springframework.lang.Nullable;\nimport org.springframework.util.MimeType;\nimport org.springframework.web.reactive.result.view.AbstractUrlBasedView;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\npublic class PebbleReactiveView extends AbstractUrlBasedView {\n\n  private static final String BEANS_VARIABLE_NAME = \"beans\";\n  private static final String REQUEST_VARIABLE_NAME = \"request\";\n  private static final String RESPONSE_VARIABLE_NAME = \"response\";\n  private static final String SESSION_VARIABLE_NAME = \"session\";\n\n  private PebbleEngine pebbleEngine;\n  private String templateName;\n\n  @Override\n  public boolean checkResourceExists(Locale locale) {\n    return this.pebbleEngine.getLoader().resourceExists(this.templateName);\n  }\n\n  @Override\n  protected Mono<Void> renderInternal(Map<String, Object> renderAttributes,\n                                      MediaType contentType,\n                                      ServerWebExchange exchange) {\n    DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();\n    if (this.logger.isDebugEnabled()) {\n      this.logger.debug(exchange.getLogPrefix() + \"Rendering [\" + this.getUrl() + \"]\");\n    }\n\n    Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());\n    try {\n      Charset charset = this.getCharset(contentType);\n      Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset);\n      this.evaluateTemplate(renderAttributes, locale, writer);\n    } catch (Exception ex) {\n      DataBufferUtils.release(dataBuffer);\n      return Mono.error(ex);\n    }\n    return exchange.getResponse().writeWith(Flux.just(dataBuffer));\n  }\n\n  @Override\n  protected Mono<Map<String, Object>> getModelAttributes(Map<String, ?> model, ServerWebExchange exchange) {\n    return super.getModelAttributes(addVariablesToModel(model, exchange), exchange);\n  }\n\n  private Map<String, ?> addVariablesToModel(Map<String, ?> model, ServerWebExchange exchange) {\n    Map<String, Object> attributes = new HashMap<>(Objects.requireNonNullElseGet(model, Map::of));\n    attributes.put(BEANS_VARIABLE_NAME, new Beans(this.getApplicationContext()));\n    attributes.put(REQUEST_VARIABLE_NAME, exchange.getRequest());\n    attributes.put(RESPONSE_VARIABLE_NAME, exchange.getResponse());\n    attributes.put(SESSION_VARIABLE_NAME, exchange.getSession());\n    return attributes;\n  }\n\n  private Charset getCharset(@Nullable MediaType mediaType) {\n    return ofNullable(mediaType)\n        .map(MimeType::getCharset)\n        .orElse(this.getDefaultCharset());\n  }\n\n  private void evaluateTemplate(Map<String, Object> model, Locale locale, Writer writer)\n      throws IOException, PebbleException {\n    try {\n      PebbleTemplate template = this.pebbleEngine.getTemplate(this.templateName);\n      template.evaluate(writer, model, locale);\n    } finally {\n      writer.flush();\n    }\n  }\n\n  public PebbleEngine getPebbleEngine() {\n    return this.pebbleEngine;\n  }\n\n  public void setPebbleEngine(PebbleEngine pebbleEngine) {\n    this.pebbleEngine = pebbleEngine;\n  }\n\n  public String getTemplateName() {\n    return this.templateName;\n  }\n\n  public void setTemplateName(String templateName) {\n    this.templateName = templateName;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/reactive/PebbleReactiveViewResolver.java",
    "content": "package io.pebbletemplates.spring.reactive;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.springframework.web.reactive.result.view.AbstractUrlBasedView;\nimport org.springframework.web.reactive.result.view.UrlBasedViewResolver;\n\npublic class PebbleReactiveViewResolver extends UrlBasedViewResolver {\n\n  private final PebbleEngine pebbleEngine;\n\n  public PebbleReactiveViewResolver(PebbleEngine pebbleEngine) {\n    this.setViewClass(this.requiredViewClass());\n    this.pebbleEngine = pebbleEngine;\n  }\n\n  @Override\n  protected AbstractUrlBasedView createView(String viewName) {\n    PebbleReactiveView view = (PebbleReactiveView) super.createView(viewName);\n    view.setPebbleEngine(this.pebbleEngine);\n    view.setTemplateName(viewName);\n\n    return view;\n  }\n\n  @Override\n  protected Class<?> requiredViewClass() {\n    return PebbleReactiveView.class;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/servlet/PebbleView.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.servlet;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.spring.context.Beans;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.support.RequestContextUtils;\nimport org.springframework.web.servlet.view.AbstractTemplateView;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.Locale;\nimport java.util.Map;\n\npublic class PebbleView extends AbstractTemplateView {\n\n  public static final String REQUEST_VARIABLE_NAME = \"request\";\n  public static final String RESPONSE_VARIABLE_NAME = \"response\";\n  public static final String SESSION_VARIABLE_NAME = \"session\";\n\n  private static final String BEANS_VARIABLE_NAME = \"beans\";\n  private static final int NANO_PER_SECOND = 1000000;\n  /**\n   * <p>\n   * TIMER logger. This logger will output the time required for executing each template processing\n   * operation.\n   * </p>\n   * <p>\n   * The value of this constant is\n   * <tt>io.pebbletemplates.servlet.spring.PebbleView.timer</tt>. This allows\n   * you to set a specific configuration and/or appenders for timing info at your logging system\n   * configuration.\n   * </p>\n   */\n  private static final Logger TIMER_LOGGER = LoggerFactory\n      .getLogger(PebbleView.class.getName() + \".timer\");\n\n  private String characterEncoding = \"UTF-8\";\n  private PebbleEngine pebbleEngine;\n  private String templateName;\n\n  @Override\n  protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request,\n      HttpServletResponse response) throws Exception {\n    long startNanoTime = System.nanoTime();\n\n    this.setCharacterEncoding(response);\n    this.addVariablesToModel(model, request, response);\n    this.evaluateTemplate(model, request, response);\n    this.logElapsedTime(startNanoTime, request);\n  }\n\n  private void setCharacterEncoding(HttpServletResponse response) {\n    if (this.characterEncoding != null) {\n      response.setCharacterEncoding(this.characterEncoding);\n    }\n  }\n\n  private void addVariablesToModel(Map<String, Object> model, HttpServletRequest request,\n      HttpServletResponse response) {\n    model.put(BEANS_VARIABLE_NAME, new Beans(this.getApplicationContext()));\n    model.put(REQUEST_VARIABLE_NAME, request);\n    model.put(RESPONSE_VARIABLE_NAME, response);\n    model.put(SESSION_VARIABLE_NAME, request.getSession(false));\n  }\n\n  private void evaluateTemplate(Map<String, Object> model, HttpServletRequest request,\n      HttpServletResponse response) throws IOException, PebbleException {\n    Locale locale = RequestContextUtils.getLocale(request);\n\n    Writer writer = response.getWriter();\n    try {\n      PebbleTemplate template = this.pebbleEngine.getTemplate(this.templateName);\n      template.evaluate(writer, model, locale);\n    } finally {\n      writer.flush();\n    }\n  }\n\n  private void logElapsedTime(long startNanoTime, HttpServletRequest request) {\n    if (TIMER_LOGGER.isDebugEnabled()) {\n      Locale locale = RequestContextUtils.getLocale(request);\n      long endNanoTime = System.nanoTime();\n\n      long elapsed = endNanoTime - startNanoTime;\n      long elapsedMs = elapsed / NANO_PER_SECOND;\n      TIMER_LOGGER\n          .debug(\"Pebble template \\\"{}\\\" with locale {} processed in {} nanoseconds (approx. {}ms)\",\n              this.templateName, locale, elapsed, elapsedMs);\n    }\n  }\n\n  public void setCharacterEncoding(String characterEncoding) {\n    this.characterEncoding = characterEncoding;\n  }\n\n  public void setPebbleEngine(PebbleEngine pebbleEngine) {\n    this.pebbleEngine = pebbleEngine;\n  }\n\n  public void setTemplateName(String name) {\n    this.templateName = name;\n  }\n}"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/main/java/io/pebbletemplates/spring/servlet/PebbleViewResolver.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.servlet;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.web.servlet.view.AbstractTemplateViewResolver;\nimport org.springframework.web.servlet.view.AbstractUrlBasedView;\n\npublic class PebbleViewResolver extends AbstractTemplateViewResolver implements InitializingBean {\n\n  private String characterEncoding = \"UTF-8\";\n  private final PebbleEngine pebbleEngine;\n\n  public PebbleViewResolver(PebbleEngine pebbleEngine) {\n    this.pebbleEngine = pebbleEngine;\n    this.setViewClass(this.requiredViewClass());\n  }\n\n  @Override\n  public void afterPropertiesSet() {\n    Loader<?> templateLoader = this.pebbleEngine.getLoader();\n    templateLoader.setPrefix(this.getPrefix());\n    templateLoader.setSuffix(this.getSuffix());\n  }\n\n  public void setCharacterEncoding(String characterEncoding) {\n    this.characterEncoding = characterEncoding;\n  }\n\n  @Override\n  protected AbstractUrlBasedView buildView(String viewName) throws Exception {\n    PebbleView view = (PebbleView) super.buildView(viewName);\n    view.setTemplateName(viewName);\n    view.setPebbleEngine(this.pebbleEngine);\n    view.setCharacterEncoding(this.characterEncoding);\n\n    return view;\n  }\n\n  @Override\n  protected Class<?> requiredViewClass() {\n    return PebbleView.class;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/java/io/pebbletemplates/spring/PebbleViewResolverTest.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring;\n\nimport io.pebbletemplates.spring.config.MVCConfig;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.mock.web.MockHttpServletRequest;\nimport org.springframework.mock.web.MockHttpServletResponse;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\nimport org.springframework.test.context.web.WebAppConfiguration;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\nimport org.springframework.validation.ObjectError;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\nimport org.springframework.web.servlet.ViewResolver;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit test for the PebbleViewResolver\n *\n * @author Eric Bussieres\n */\n@ExtendWith(SpringExtension.class)\n@WebAppConfiguration\n@ContextConfiguration(classes = MVCConfig.class)\nclass PebbleViewResolverTest {\n\n  private static final String CONTEXT_PATH = \"/testContextPath\";\n  private static final Locale DEFAULT_LOCALE = Locale.CANADA;\n  private static final String EXPECTED_RESPONSE_PATH = \"/io/pebbletemplates/spring/expectedResponse\";\n  private static final String FORM_NAME = \"formName\";\n\n  private BindingResult mockBindingResult = mock(BindingResult.class);\n  private MockHttpServletRequest mockRequest = new MockHttpServletRequest();\n  private MockHttpServletResponse mockResponse = new MockHttpServletResponse();\n\n  @Autowired\n  private ViewResolver viewResolver;\n\n  @BeforeEach\n  void initRequest() {\n    this.mockRequest.setContextPath(CONTEXT_PATH);\n    this.mockRequest.getSession().setMaxInactiveInterval(600);\n\n    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.mockRequest));\n  }\n\n  @BeforeEach\n  void initBindingResult() {\n    this.initBindingResultAllErrors();\n    this.initBindingResultGlobalErrors();\n    this.initBindingResultFieldErrors();\n  }\n\n  private void initBindingResultAllErrors() {\n    when(this.mockBindingResult.hasErrors()).thenReturn(true);\n\n    List<ObjectError> allErrors = new ArrayList<>();\n    allErrors.add(\n        new ObjectError(FORM_NAME, new String[]{\"error.test\"}, new String[]{}, \"???error.test???\"));\n    when(this.mockBindingResult.getAllErrors()).thenReturn(allErrors);\n  }\n\n  private void initBindingResultGlobalErrors() {\n    when(this.mockBindingResult.hasGlobalErrors()).thenReturn(true);\n\n    List<ObjectError> globalErrors = new ArrayList<>();\n    globalErrors.add(new ObjectError(FORM_NAME, new String[]{\"error.global.test.params\"},\n        new String[]{\"param1\", \"param2\"}, \"???error.global.test.params???\"));\n    when(this.mockBindingResult.getGlobalErrors()).thenReturn(globalErrors);\n  }\n\n  private void initBindingResultFieldErrors() {\n    when(this.mockBindingResult.hasFieldErrors(\"testField\")).thenReturn(true);\n\n    List<FieldError> fieldErrors = new ArrayList<>();\n    fieldErrors.add(\n        new FieldError(FORM_NAME, \"testField\", null, false, new String[]{\"error.field.test.params\"},\n            new String[]{\"param1\", \"param2\"}, \"???error.field.test.params???\"));\n    when(this.mockBindingResult.getFieldErrors(\"testField\")).thenReturn(fieldErrors);\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithBeanVariable_thenRenderingIsOK() throws Exception {\n    String result = this.render(\"beansTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/beansTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithBindingResult_thenRenderingIsOK() throws Exception {\n    Map<String, Object> model = this.givenBindingResult();\n\n    String result = this.render(\"bindingResultTest\", model);\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/bindingResultTest.html\");\n  }\n\n  private Map<String, Object> givenBindingResult() {\n    Map<String, Object> model = new HashMap<>();\n    model.put(BindingResult.MODEL_KEY_PREFIX + FORM_NAME, this.mockBindingResult);\n    return model;\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithBindingResultAndMacro_thenRenderingIsOK() throws Exception {\n    Map<String, Object> model = this.givenBindingResult();\n\n    String result = this.render(\"bindingResultWithMacroTest\", model);\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/bindingResultWithMacroTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHrefFunction_thenRenderingIsOK() throws Exception {\n    String result = this.render(\"hrefFunctionTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/hrefFunctionTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPageInEnglish_givenPageWithResourceBundleMessage_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"messageEnTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/messageEnTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPageInFrench_givenPageWithResourceBundleMessage_thenRenderingIsOK()\n      throws Exception {\n    this.mockRequest.addPreferredLocale(Locale.CANADA_FRENCH);\n\n    String result = this.render(\"messageFrTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/messageFrTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHttpRequestVariable_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"requestTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/requestTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHttpResponseVariable_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"responseTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/responseTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHttpSessionVariable_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"sessionTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/sessionTest.html\");\n  }\n\n  private void assertOutput(String output, String expectedOutput) throws IOException {\n    assertEquals(this.readExpectedOutputResource(expectedOutput), output.replaceAll(\"\\\\s\", \"\"));\n  }\n\n  private String readExpectedOutputResource(String expectedOutput) throws IOException {\n    BufferedReader reader = new BufferedReader(\n        new InputStreamReader(this.getClass().getResourceAsStream(expectedOutput)));\n\n    StringBuilder builder = new StringBuilder();\n    for (String currentLine = reader.readLine(); currentLine != null;\n        currentLine = reader.readLine()) {\n      builder.append(currentLine);\n    }\n\n    return this.removeAllWhitespaces(builder.toString());\n  }\n\n  private String removeAllWhitespaces(String source) {\n    return source.replaceAll(\"\\\\s\", \"\");\n  }\n\n  private String render(String location, Map<String, ?> model) throws Exception {\n    this.viewResolver.resolveViewName(location, DEFAULT_LOCALE)\n        .render(model, this.mockRequest, this.mockResponse);\n    return this.mockResponse.getContentAsString();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/java/io/pebbletemplates/spring/bean/SomeBean.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.bean;\n\n/**\n * Spring bean for unit test\n *\n * @author Eric Bussieres\n */\npublic class SomeBean {\n\n  public String foo() {\n    return \"foo\";\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/java/io/pebbletemplates/spring/config/MVCConfig.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.config;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.spring.bean.SomeBean;\nimport io.pebbletemplates.spring.extension.SpringExtension;\nimport io.pebbletemplates.spring.servlet.PebbleViewResolver;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.support.ResourceBundleMessageSource;\nimport org.springframework.web.servlet.ViewResolver;\n\n/**\n * Spring configuration for unit test\n *\n * @author Eric Bussieres\n */\n@Configuration(proxyBeanMethods = false)\npublic class MVCConfig {\n\n  @Bean\n  public SomeBean foo() {\n    return new SomeBean();\n  }\n\n  @Bean\n  public MessageSource messageSource() {\n    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();\n    messageSource.setBasename(\"io.pebbletemplates.spring.messages\");\n\n    return messageSource;\n  }\n\n  @Bean\n  public PebbleEngine pebbleEngine(SpringExtension springExtension,\n                                   Loader<?> templateLoader) {\n    return new PebbleEngine.Builder()\n        .loader(templateLoader)\n        .strictVariables(false)\n        .extension(springExtension)\n        .build();\n  }\n\n  @Bean\n  public SpringExtension springExtension(MessageSource messageSource) {\n    return new SpringExtension(messageSource);\n  }\n\n  @Bean\n  public Loader<?> templateLoader() {\n    return new ClasspathLoader();\n  }\n\n  @Bean\n  public ViewResolver viewResolver(PebbleEngine pebbleEngine) {\n    PebbleViewResolver viewResolver = new PebbleViewResolver(pebbleEngine);\n    viewResolver.setPrefix(\"io/pebbletemplates/spring/template/\");\n    viewResolver.setSuffix(\".html\");\n    viewResolver.setContentType(\"text/html\");\n    return viewResolver;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/beansTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>foo</title>\n</head>\n\n<body>\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/bindingResultTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\ntrue\ntrue\ntrue\n\nfalse\nfalse\nfalse\n\nSome error\nSome global error with params param1 and param2\nSome field error with params param1 and param2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/bindingResultWithMacroTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\ntrue\ntrue\ntrue\n\nfalse\nfalse\nfalse\n\nSome error\nSome global error with params param1 and param2\nSome field error with params param1 and param2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/hrefFunctionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nHrefFunction string interpolation = /testContextPath/foo\nHrefFunction static = /testContextPath/foobar\nHrefFunction expression = /testContextPath/foo\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/messageEnTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = Some label\nLabel with params = Some label with params params1 and params2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/messageFrTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = Un libellé\nLabel with params = Un libellé avec les paramètres params1 et params2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/requestTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nContext path = /testContextPath\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/responseTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nResponse contentType = text/html;charset=UTF-8\nResponse character encoding = UTF-8\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/expectedResponse/sessionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nSession maxInactiveInterval = 600\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/messages_en.properties",
    "content": "label.test=Some label\nlabel.test.params=Some label with params {0} and {1}\nerror.test=Some error\nerror.global.test.params=Some global error with params {0} and {1}\nerror.field.test.params=Some field error with params {0} and {1}"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/messages_fr.properties",
    "content": "label.test=Un libell\nlabel.test.params=Un libell avec les paramtres {0} et {1}\nerror.test=Une erreur\nerror.global.test.params=Une erreur globale avec les paramtres {0} et {1}\nerror.field.test.params=Une erreur sur un champ avec les paramtres {0} et {1}"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/beansTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>{{ beans.foo.foo() }}</title>\n</head>\n\n<body>\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/bindingResultTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\n{{ hasErrors('formName') }}\n{{ hasGlobalErrors('formName') }}\n{{ hasFieldErrors('formName', 'testField') }}\n\n{{ hasErrors('') }}\n{{ hasGlobalErrors('') }}\n{{ hasFieldErrors('formName', '') }}\n\n{% for err in getAllErrors('formName') %}{{ err }}{% endfor %}\n{% for err in getGlobalErrors('formName') %}{{ err }}{% endfor %}\n{% for err in getFieldErrors('formName', 'testField') %}{{ err }}{% endfor %}\n\n{% for err in getAllErrors('') %}{{ err }}{% endfor %}\n{% for err in getGlobalErrors('') %}{{ err }}{% endfor %}\n{% for err in getFieldErrors('', 'testField') %}{{ err }}{% endfor %}\n{% for err in getFieldErrors('formName', '') %}{{ err }}{% endfor %}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/bindingResultWithMacroTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\n{{ test(_context) }}\n{% macro test(_context) %}\n  {{ hasErrors('formName') }}\n  {{ hasGlobalErrors('formName') }}\n  {{ hasFieldErrors('formName', 'testField') }}\n\n  {{ hasErrors('') }}\n  {{ hasGlobalErrors('') }}\n  {{ hasFieldErrors('formName', '') }}\n\n  {% for err in getAllErrors('formName') %}{{ err }}{% endfor %}\n  {% for err in getGlobalErrors('formName') %}{{ err }}{% endfor %}\n  {% for err in getFieldErrors('formName', 'testField') %}{{ err }}{% endfor %}\n\n  {% for err in getAllErrors('') %}{{ err }}{% endfor %}\n  {% for err in getGlobalErrors('') %}{{ err }}{% endfor %}\n  {% for err in getFieldErrors('', 'testField') %}{{ err }}{% endfor %}\n  {% for err in getFieldErrors('formName', '') %}{{ err }}{% endfor %}\n{% endmacro %}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/hrefFunctionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nHrefFunction string interpolation = {{ href(\"/#{beans.foo.foo}\") }}\nHrefFunction static = {{ href('/foobar') }}\nHrefFunction expression = {{ href('/' + beans.foo.foo) }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/messageEnTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = {{ message('label.test') }}\nLabel with params = {{ message('label.test.params', 'params1', 'params2') }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/messageFrTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = {{ message('label.test') }}\nLabel with params = {{ message('label.test.params', 'params1', 'params2') }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/requestTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nContext path = {{ request.contextPath }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/responseTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nResponse contentType = {{ response.contentType }}\nResponse character encoding = {{ response.characterEncoding }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring6/src/test/resources/io/pebbletemplates/spring/template/sessionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nSession maxInactiveInterval = {{ session.maxInactiveInterval }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <artifactId>pebble-spring</artifactId>\n    <groupId>io.pebbletemplates</groupId>\n    <version>4.1.2-SNAPSHOT</version>\n  </parent>\n\n  <artifactId>pebble-spring7</artifactId>\n\n  <name>Pebble Integration with Spring 7.x</name>\n  <description>Pebble Integration with Spring 7.x</description>\n  <url>http://pebbletemplates.io</url>\n\n  <properties>\n    <java.version>17</java.version>\n    <servlet-api.version>6.1.0</servlet-api.version>\n    <spring-framework.version>7.0.7</spring-framework.version>\n    <mockito-junit-jupiter.version>5.23.0</mockito-junit-jupiter.version>\n    <junit-jupiter.version>6.0.3</junit-jupiter.version>\n  </properties>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>org.springframework</groupId>\n        <artifactId>spring-framework-bom</artifactId>\n        <version>${spring-framework.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <dependencies>\n    <dependency>\n      <groupId>io.pebbletemplates</groupId>\n      <artifactId>pebble</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>jakarta.servlet</groupId>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <version>${servlet-api.version}</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-webmvc</artifactId>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-webflux</artifactId>\n      <scope>provided</scope>\n    </dependency>\n\n    <!-- TEST -->\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-test</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter</artifactId>\n      <version>${junit-jupiter.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-junit-jupiter</artifactId>\n      <version>${mockito-junit-jupiter.version}</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifestEntries>\n              <Automatic-Module-Name>io.pebbletemplates.spring</Automatic-Module-Name>\n            </manifestEntries>\n          </archive>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/context/Beans.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.context;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.util.Assert;\n\n/**\n * <p>\n * Special object made available to templates in Spring MVC applications in order to access beans in\n * the Application Context.\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class Beans implements Map<String, Object> {\n\n  private final ApplicationContext ctx;\n\n  public Beans(ApplicationContext ctx) {\n    Assert.notNull(ctx, \"Application Context cannot be null\");\n    this.ctx = ctx;\n  }\n\n  @Override\n  public void clear() {\n    throw new UnsupportedOperationException(\"Method \\\"clear\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public boolean containsKey(Object key) {\n    Assert.notNull(key, \"Key cannot be null\");\n    return this.ctx.containsBean(key.toString());\n  }\n\n  @Override\n  public boolean containsValue(Object value) {\n    throw new UnsupportedOperationException(\n        \"Method \\\"containsValue\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public Set<Entry<String, Object>> entrySet() {\n    throw new UnsupportedOperationException(\"Method \\\"entrySet\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public Object get(Object key) {\n    Assert.notNull(key, \"Key cannot be null\");\n    return this.ctx.getBean(key.toString());\n  }\n\n  @Override\n  public boolean isEmpty() {\n    return this.size() <= 0;\n  }\n\n  @Override\n  public Set<String> keySet() {\n    return new LinkedHashSet<>(Arrays.asList(this.ctx.getBeanDefinitionNames()));\n  }\n\n  @Override\n  public Object put(String key, Object value) {\n    throw new UnsupportedOperationException(\"Method \\\"put\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public void putAll(Map<? extends String, ?> m) {\n    throw new UnsupportedOperationException(\"Method \\\"putAll\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public Object remove(Object key) {\n    throw new UnsupportedOperationException(\"Method \\\"remove\\\" not supported in Beans object\");\n  }\n\n  @Override\n  public int size() {\n    return this.ctx.getBeanDefinitionCount();\n  }\n\n  @Override\n  public Collection<Object> values() {\n    throw new UnsupportedOperationException(\"Method \\\"values\\\" not supported in Beans object\");\n  }\n\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/SpringExtension.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension;\n\nimport io.pebbletemplates.pebble.extension.AbstractExtension;\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.spring.extension.function.HrefFunction;\nimport io.pebbletemplates.spring.extension.function.MessageSourceFunction;\nimport io.pebbletemplates.spring.extension.function.bindingresult.*;\nimport org.springframework.context.MessageSource;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * <p>\n * Extension for PebbleEngine to add spring functionality\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class SpringExtension extends AbstractExtension {\n\n  private final MessageSource messageSource;\n\n  public SpringExtension(MessageSource messageSource) {\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Map<String, Function> getFunctions() {\n    Map<String, Function> functions = new HashMap<>();\n    functions\n        .put(MessageSourceFunction.FUNCTION_NAME, new MessageSourceFunction(this.messageSource));\n    functions.put(HasErrorsFunction.FUNCTION_NAME, new HasErrorsFunction());\n    functions.put(HasGlobalErrorsFunction.FUNCTION_NAME, new HasGlobalErrorsFunction());\n    functions.put(HasFieldErrorsFunction.FUNCTION_NAME, new HasFieldErrorsFunction());\n    functions.put(GetAllErrorsFunction.FUNCTION_NAME, new GetAllErrorsFunction(this.messageSource));\n    functions.put(GetGlobalErrorsFunction.FUNCTION_NAME,\n        new GetGlobalErrorsFunction(this.messageSource));\n    functions\n        .put(GetFieldErrorsFunction.FUNCTION_NAME, new GetFieldErrorsFunction(this.messageSource));\n    functions.put(HrefFunction.FUNCTION_NAME, new HrefFunction());\n    return functions;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/HrefFunction.java",
    "content": "package io.pebbletemplates.spring.extension.function;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.pebble.extension.NamedArguments;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Pebble function which adds the context path to the given url\n *\n * @author Eric Bussieres\n */\npublic class HrefFunction implements Function {\n\n  public static final String FUNCTION_NAME = \"href\";\n\n  protected static final String PARAM_URL = \"url\";\n\n  protected List<String> argumentNames;\n  private String contextPath;\n\n  /**\n   * Constructor\n   */\n  public HrefFunction() {\n    this.argumentNames = new ArrayList<>();\n    this.argumentNames.add(PARAM_URL);\n  }\n\n  /**\n   * {@inheritDoc}\n   *\n   * @see Function#execute(Map, PebbleTemplate, EvaluationContext, int)\n   */\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n      int lineNumber) {\n    StringBuffer result = new StringBuffer();\n\n    result.append(this.getContextPath());\n    this.addUrlParameter(args, result);\n\n    return result.toString();\n  }\n\n  private void addUrlParameter(Map<String, Object> args, StringBuffer result) {\n    String url = (String) args.get(PARAM_URL);\n    if (StringUtils.hasText(url)) {\n      result.append(url);\n    }\n  }\n\n  private String getContextPath() {\n    if (this.contextPath == null) {\n      this.contextPath = this.getRequest().getContextPath();\n    }\n    return this.contextPath;\n  }\n\n  private HttpServletRequest getRequest() {\n    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();\n    return attr.getRequest();\n  }\n\n  /**\n   * {@inheritDoc}\n   *\n   * @see NamedArguments#getArgumentNames()\n   */\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/MessageSourceFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to resolve message in the\n * application context\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class MessageSourceFunction implements Function {\n\n  public static final String FUNCTION_NAME = \"message\";\n\n  private final MessageSource messageSource;\n\n  public MessageSourceFunction(MessageSource messageSource) {\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String key = this.extractKey(args);\n    List<Object> arguments = this.extractArguments(args);\n    Locale locale = context.getLocale();\n\n    return this.messageSource.getMessage(key, arguments.toArray(), \"???\" + key + \"???\", locale);\n  }\n\n  private String extractKey(Map<String, Object> args) {\n    return (String) args.get(\"0\");\n  }\n\n  private List<Object> extractArguments(Map<String, Object> args) {\n    int i = 1;\n    List<Object> arguments = new ArrayList<>();\n    while (args.containsKey(String.valueOf(i))) {\n      Object param = args.get(String.valueOf(i));\n      arguments.add(param);\n      i++;\n    }\n    return arguments;\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return null;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/BaseBindingResultFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.extension.Function;\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.GlobalContext;\n\nimport org.springframework.validation.BindingResult;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Base class of the function interacting with the BindingResult\n *\n * @author Eric Bussieres\n */\npublic abstract class BaseBindingResultFunction implements Function {\n\n  protected static final String PARAM_FIELD_NAME = \"fieldName\";\n  protected static final String PARAM_FORM_NAME = \"formName\";\n\n  private final List<String> argumentNames = new ArrayList<>();\n\n  protected BaseBindingResultFunction(String... argumentsName) {\n    Collections.addAll(this.argumentNames, argumentsName);\n  }\n\n  @Override\n  public List<String> getArgumentNames() {\n    return this.argumentNames;\n  }\n\n  protected BindingResult getBindingResult(String formName, EvaluationContext context) {\n    String attribute = BindingResult.MODEL_KEY_PREFIX + formName;\n    BindingResult bindingResult = (BindingResult) context.getVariable(attribute);\n    if (bindingResult == null) {\n      GlobalContext globalContext = (GlobalContext) context.getVariable(\"_context\");\n      if (globalContext != null) {\n        return (BindingResult) globalContext.get(attribute);\n      }\n    }\n    return bindingResult;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/GetAllErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.ObjectError;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class GetAllErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"getAllErrors\";\n\n  private final MessageSource messageSource;\n\n  public GetAllErrorsFunction(MessageSource messageSource) {\n    super(PARAM_FORM_NAME);\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    Locale locale = context.getLocale();\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    return this.constructErrorMessage(locale, bindingResult);\n  }\n\n  private List<String> constructErrorMessage(Locale locale, BindingResult bindingResult) {\n    List<String> errorMessages = new ArrayList<>();\n    if (bindingResult != null) {\n      for (ObjectError error : bindingResult.getAllErrors()) {\n        String msg = this.messageSource.getMessage(error, locale);\n        errorMessages.add(msg);\n      }\n    }\n    return errorMessages;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/GetFieldErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class GetFieldErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"getFieldErrors\";\n\n  private final MessageSource messageSource;\n\n  public GetFieldErrorsFunction(MessageSource messageSource) {\n    super(PARAM_FORM_NAME, PARAM_FIELD_NAME);\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n    String field = (String) args.get(PARAM_FIELD_NAME);\n\n    if (field == null) {\n      throw new IllegalArgumentException(\"Field parameter is required in GetFieldErrorsFunction\");\n    }\n\n    Locale locale = context.getLocale();\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    return this.constructErrorMessages(field, locale, bindingResult);\n  }\n\n  private List<String> constructErrorMessages(String field, Locale locale,\n      BindingResult bindingResult) {\n    List<String> errorMessages = new ArrayList<>();\n    if (bindingResult != null) {\n      for (FieldError error : bindingResult.getFieldErrors(field)) {\n        String msg = this.messageSource.getMessage(error, locale);\n        errorMessages.add(msg);\n      }\n    }\n    return errorMessages;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/GetGlobalErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport org.springframework.context.MessageSource;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.ObjectError;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class GetGlobalErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"getGlobalErrors\";\n\n  private final MessageSource messageSource;\n\n  public GetGlobalErrorsFunction(MessageSource messageSource) {\n    super(PARAM_FORM_NAME);\n    this.messageSource = messageSource;\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    Locale locale = context.getLocale();\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    return this.constructErrorMessages(locale, bindingResult);\n  }\n\n  private List<String> constructErrorMessages(Locale locale, BindingResult bindingResult) {\n    List<String> errorMessages = new ArrayList<>();\n    if (bindingResult != null) {\n      for (ObjectError error : bindingResult.getGlobalErrors()) {\n        String msg = this.messageSource.getMessage(error, locale);\n        errorMessages.add(msg);\n      }\n    }\n    return errorMessages;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/HasErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.Map;\nimport org.springframework.validation.BindingResult;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class HasErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"hasErrors\";\n\n  public HasErrorsFunction() {\n    super(PARAM_FORM_NAME);\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n    return bindingResult != null && bindingResult.hasErrors();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/HasFieldErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.Map;\nimport org.springframework.validation.BindingResult;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class HasFieldErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"hasFieldErrors\";\n\n  public HasFieldErrorsFunction() {\n    super(PARAM_FORM_NAME, PARAM_FIELD_NAME);\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n    String fieldName = (String) args.get(PARAM_FIELD_NAME);\n\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n\n    if (bindingResult != null) {\n      if (fieldName == null) {\n        return bindingResult.hasFieldErrors();\n      } else {\n        return bindingResult.hasFieldErrors(fieldName);\n      }\n    } else {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/extension/function/bindingresult/HasGlobalErrorsFunction.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.extension.function.bindingresult;\n\nimport io.pebbletemplates.pebble.template.EvaluationContext;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport java.util.Map;\nimport org.springframework.validation.BindingResult;\n\n/**\n * <p>\n * Function available to templates in Spring MVC applications in order to access the BindingResult\n * of a form\n * </p>\n *\n * @author Eric Bussieres\n */\npublic class HasGlobalErrorsFunction extends BaseBindingResultFunction {\n\n  public static final String FUNCTION_NAME = \"hasGlobalErrors\";\n\n  public HasGlobalErrorsFunction() {\n    super(PARAM_FORM_NAME);\n  }\n\n  @Override\n  public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context,\n                        int lineNumber) {\n    String formName = (String) args.get(PARAM_FORM_NAME);\n\n    BindingResult bindingResult = this.getBindingResult(formName, context);\n    return bindingResult != null && bindingResult.hasGlobalErrors();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/reactive/PebbleReactiveView.java",
    "content": "package io.pebbletemplates.spring.reactive;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.spring.context.Beans;\nimport org.springframework.context.i18n.LocaleContextHolder;\nimport org.springframework.core.io.buffer.DataBuffer;\nimport org.springframework.core.io.buffer.DataBufferUtils;\nimport org.springframework.http.MediaType;\nimport org.springframework.lang.Nullable;\nimport org.springframework.util.MimeType;\nimport org.springframework.web.reactive.result.view.AbstractUrlBasedView;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.nio.charset.Charset;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static java.util.Optional.ofNullable;\n\npublic class PebbleReactiveView extends AbstractUrlBasedView {\n\n  private static final String BEANS_VARIABLE_NAME = \"beans\";\n  private static final String REQUEST_VARIABLE_NAME = \"request\";\n  private static final String RESPONSE_VARIABLE_NAME = \"response\";\n  private static final String SESSION_VARIABLE_NAME = \"session\";\n\n  private PebbleEngine pebbleEngine;\n  private String templateName;\n\n  @Override\n  public boolean checkResourceExists(Locale locale) {\n    return this.pebbleEngine.getLoader().resourceExists(this.templateName);\n  }\n\n  @Override\n  protected Mono<Void> renderInternal(Map<String, Object> renderAttributes,\n                                      MediaType contentType,\n                                      ServerWebExchange exchange) {\n    DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();\n    if (this.logger.isDebugEnabled()) {\n      this.logger.debug(exchange.getLogPrefix() + \"Rendering [\" + this.getUrl() + \"]\");\n    }\n\n    Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());\n    try {\n      Charset charset = this.getCharset(contentType);\n      Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset);\n      this.evaluateTemplate(renderAttributes, locale, writer);\n    } catch (Exception ex) {\n      DataBufferUtils.release(dataBuffer);\n      return Mono.error(ex);\n    }\n    return exchange.getResponse().writeWith(Flux.just(dataBuffer));\n  }\n\n  @Override\n  protected Mono<Map<String, Object>> getModelAttributes(Map<String, ?> model, ServerWebExchange exchange) {\n    return super.getModelAttributes(this.addVariablesToModel(model, exchange), exchange);\n  }\n\n  private Map<String, ?> addVariablesToModel(Map<String, ?> model, ServerWebExchange exchange) {\n    Map<String, Object> attributes = new HashMap<>(Objects.requireNonNullElseGet(model, Map::of));\n    attributes.put(BEANS_VARIABLE_NAME, new Beans(this.getApplicationContext()));\n    attributes.put(REQUEST_VARIABLE_NAME, exchange.getRequest());\n    attributes.put(RESPONSE_VARIABLE_NAME, exchange.getResponse());\n    attributes.put(SESSION_VARIABLE_NAME, exchange.getSession());\n    return attributes;\n  }\n\n  private Charset getCharset(@Nullable MediaType mediaType) {\n    return ofNullable(mediaType)\n            .map(MimeType::getCharset)\n            .orElse(this.getDefaultCharset());\n  }\n\n  private void evaluateTemplate(Map<String, Object> model, Locale locale, Writer writer)\n          throws IOException, PebbleException {\n    try {\n      PebbleTemplate template = this.pebbleEngine.getTemplate(this.templateName);\n      template.evaluate(writer, model, locale);\n    } finally {\n      writer.flush();\n    }\n  }\n\n  public PebbleEngine getPebbleEngine() {\n    return this.pebbleEngine;\n  }\n\n  public void setPebbleEngine(PebbleEngine pebbleEngine) {\n    this.pebbleEngine = pebbleEngine;\n  }\n\n  public String getTemplateName() {\n    return this.templateName;\n  }\n\n  public void setTemplateName(String templateName) {\n    this.templateName = templateName;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/reactive/PebbleReactiveViewResolver.java",
    "content": "package io.pebbletemplates.spring.reactive;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport org.springframework.web.reactive.result.view.AbstractUrlBasedView;\nimport org.springframework.web.reactive.result.view.UrlBasedViewResolver;\n\npublic class PebbleReactiveViewResolver extends UrlBasedViewResolver {\n\n  private final PebbleEngine pebbleEngine;\n\n  public PebbleReactiveViewResolver(PebbleEngine pebbleEngine) {\n    this.setViewClass(this.requiredViewClass());\n    this.pebbleEngine = pebbleEngine;\n  }\n\n  @Override\n  protected AbstractUrlBasedView createView(String viewName) {\n    PebbleReactiveView view = (PebbleReactiveView) super.createView(viewName);\n    view.setPebbleEngine(this.pebbleEngine);\n    view.setTemplateName(viewName);\n\n    return view;\n  }\n\n  @Override\n  protected Class<?> requiredViewClass() {\n    return PebbleReactiveView.class;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/servlet/PebbleView.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.servlet;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.error.PebbleException;\nimport io.pebbletemplates.pebble.template.PebbleTemplate;\nimport io.pebbletemplates.spring.context.Beans;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.support.RequestContextUtils;\nimport org.springframework.web.servlet.view.AbstractTemplateView;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.Locale;\nimport java.util.Map;\n\npublic class PebbleView extends AbstractTemplateView {\n\n  public static final String REQUEST_VARIABLE_NAME = \"request\";\n  public static final String RESPONSE_VARIABLE_NAME = \"response\";\n  public static final String SESSION_VARIABLE_NAME = \"session\";\n\n  private static final String BEANS_VARIABLE_NAME = \"beans\";\n  private static final int NANO_PER_SECOND = 1000000;\n  /**\n   * <p>\n   * TIMER logger. This logger will output the time required for executing each template processing\n   * operation.\n   * </p>\n   * <p>\n   * The value of this constant is\n   * <tt>io.pebbletemplates.servlet.spring.PebbleView.timer</tt>. This allows\n   * you to set a specific configuration and/or appenders for timing info at your logging system\n   * configuration.\n   * </p>\n   */\n  private static final Logger TIMER_LOGGER = LoggerFactory\n      .getLogger(PebbleView.class.getName() + \".timer\");\n\n  private String characterEncoding = \"UTF-8\";\n  private PebbleEngine pebbleEngine;\n  private String templateName;\n\n  @Override\n  protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request,\n      HttpServletResponse response) throws Exception {\n    long startNanoTime = System.nanoTime();\n\n    this.setCharacterEncoding(response);\n    this.addVariablesToModel(model, request, response);\n    this.evaluateTemplate(model, request, response);\n    this.logElapsedTime(startNanoTime, request);\n  }\n\n  private void setCharacterEncoding(HttpServletResponse response) {\n    if (this.characterEncoding != null) {\n      response.setCharacterEncoding(this.characterEncoding);\n    }\n  }\n\n  private void addVariablesToModel(Map<String, Object> model, HttpServletRequest request,\n      HttpServletResponse response) {\n    model.put(BEANS_VARIABLE_NAME, new Beans(this.getApplicationContext()));\n    model.put(REQUEST_VARIABLE_NAME, request);\n    model.put(RESPONSE_VARIABLE_NAME, response);\n    model.put(SESSION_VARIABLE_NAME, request.getSession(false));\n  }\n\n  private void evaluateTemplate(Map<String, Object> model, HttpServletRequest request,\n      HttpServletResponse response) throws IOException, PebbleException {\n    Locale locale = RequestContextUtils.getLocale(request);\n\n    Writer writer = response.getWriter();\n    try {\n      PebbleTemplate template = this.pebbleEngine.getTemplate(this.templateName);\n      template.evaluate(writer, model, locale);\n    } finally {\n      writer.flush();\n    }\n  }\n\n  private void logElapsedTime(long startNanoTime, HttpServletRequest request) {\n    if (TIMER_LOGGER.isDebugEnabled()) {\n      Locale locale = RequestContextUtils.getLocale(request);\n      long endNanoTime = System.nanoTime();\n\n      long elapsed = endNanoTime - startNanoTime;\n      long elapsedMs = elapsed / NANO_PER_SECOND;\n      TIMER_LOGGER\n          .debug(\"Pebble template \\\"{}\\\" with locale {} processed in {} nanoseconds (approx. {}ms)\",\n              this.templateName, locale, elapsed, elapsedMs);\n    }\n  }\n\n  public void setCharacterEncoding(String characterEncoding) {\n    this.characterEncoding = characterEncoding;\n  }\n\n  public void setPebbleEngine(PebbleEngine pebbleEngine) {\n    this.pebbleEngine = pebbleEngine;\n  }\n\n  public void setTemplateName(String name) {\n    this.templateName = name;\n  }\n}"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/main/java/io/pebbletemplates/spring/servlet/PebbleViewResolver.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.servlet;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.web.servlet.view.AbstractTemplateViewResolver;\nimport org.springframework.web.servlet.view.AbstractUrlBasedView;\n\npublic class PebbleViewResolver extends AbstractTemplateViewResolver implements InitializingBean {\n\n  private String characterEncoding = \"UTF-8\";\n  private final PebbleEngine pebbleEngine;\n\n  public PebbleViewResolver(PebbleEngine pebbleEngine) {\n    this.pebbleEngine = pebbleEngine;\n    this.setViewClass(this.requiredViewClass());\n  }\n\n  @Override\n  public void afterPropertiesSet() {\n    Loader<?> templateLoader = this.pebbleEngine.getLoader();\n    templateLoader.setPrefix(this.getPrefix());\n    templateLoader.setSuffix(this.getSuffix());\n  }\n\n  public void setCharacterEncoding(String characterEncoding) {\n    this.characterEncoding = characterEncoding;\n  }\n\n  @Override\n  protected AbstractUrlBasedView buildView(String viewName) throws Exception {\n    PebbleView view = (PebbleView) super.buildView(viewName);\n    view.setTemplateName(viewName);\n    view.setPebbleEngine(this.pebbleEngine);\n    view.setCharacterEncoding(this.characterEncoding);\n\n    return view;\n  }\n\n  @Override\n  protected Class<?> requiredViewClass() {\n    return PebbleView.class;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/java/io/pebbletemplates/spring/PebbleViewResolverTest.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring;\n\nimport io.pebbletemplates.spring.config.MVCConfig;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.mock.web.MockHttpServletRequest;\nimport org.springframework.mock.web.MockHttpServletResponse;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\nimport org.springframework.test.context.web.WebAppConfiguration;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\nimport org.springframework.validation.ObjectError;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\nimport org.springframework.web.servlet.ViewResolver;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.*;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit test for the PebbleViewResolver\n *\n * @author Eric Bussieres\n */\n@ExtendWith(SpringExtension.class)\n@WebAppConfiguration\n@ContextConfiguration(classes = MVCConfig.class)\nclass PebbleViewResolverTest {\n\n  private static final String CONTEXT_PATH = \"/testContextPath\";\n  private static final Locale DEFAULT_LOCALE = Locale.CANADA;\n  private static final String EXPECTED_RESPONSE_PATH = \"/io/pebbletemplates/spring/expectedResponse\";\n  private static final String FORM_NAME = \"formName\";\n\n  private final BindingResult mockBindingResult = mock(BindingResult.class);\n  private final MockHttpServletRequest mockRequest = new MockHttpServletRequest();\n  private final MockHttpServletResponse mockResponse = new MockHttpServletResponse();\n\n  @Autowired\n  private ViewResolver viewResolver;\n\n  @BeforeEach\n  void initRequest() {\n    this.mockRequest.setContextPath(CONTEXT_PATH);\n    this.mockRequest.getSession().setMaxInactiveInterval(600);\n\n    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.mockRequest));\n  }\n\n  @BeforeEach\n  void initBindingResult() {\n    this.initBindingResultAllErrors();\n    this.initBindingResultGlobalErrors();\n    this.initBindingResultFieldErrors();\n  }\n\n  private void initBindingResultAllErrors() {\n    when(this.mockBindingResult.hasErrors()).thenReturn(true);\n\n    List<ObjectError> allErrors = new ArrayList<>();\n    allErrors.add(\n        new ObjectError(FORM_NAME, new String[]{\"error.test\"}, new String[]{}, \"???error.test???\"));\n    when(this.mockBindingResult.getAllErrors()).thenReturn(allErrors);\n  }\n\n  private void initBindingResultGlobalErrors() {\n    when(this.mockBindingResult.hasGlobalErrors()).thenReturn(true);\n\n    List<ObjectError> globalErrors = new ArrayList<>();\n    globalErrors.add(new ObjectError(FORM_NAME, new String[]{\"error.global.test.params\"},\n        new String[]{\"param1\", \"param2\"}, \"???error.global.test.params???\"));\n    when(this.mockBindingResult.getGlobalErrors()).thenReturn(globalErrors);\n  }\n\n  private void initBindingResultFieldErrors() {\n    when(this.mockBindingResult.hasFieldErrors(\"testField\")).thenReturn(true);\n\n    List<FieldError> fieldErrors = new ArrayList<>();\n    fieldErrors.add(\n        new FieldError(FORM_NAME, \"testField\", null, false, new String[]{\"error.field.test.params\"},\n            new String[]{\"param1\", \"param2\"}, \"???error.field.test.params???\"));\n    when(this.mockBindingResult.getFieldErrors(\"testField\")).thenReturn(fieldErrors);\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithBeanVariable_thenRenderingIsOK() throws Exception {\n    String result = this.render(\"beansTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/beansTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithBindingResult_thenRenderingIsOK() throws Exception {\n    Map<String, Object> model = this.givenBindingResult();\n\n    String result = this.render(\"bindingResultTest\", model);\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/bindingResultTest.html\");\n  }\n\n  private Map<String, Object> givenBindingResult() {\n    Map<String, Object> model = new HashMap<>();\n    model.put(BindingResult.MODEL_KEY_PREFIX + FORM_NAME, this.mockBindingResult);\n    return model;\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithBindingResultAndMacro_thenRenderingIsOK() throws Exception {\n    Map<String, Object> model = this.givenBindingResult();\n\n    String result = this.render(\"bindingResultWithMacroTest\", model);\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/bindingResultWithMacroTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHrefFunction_thenRenderingIsOK() throws Exception {\n    String result = this.render(\"hrefFunctionTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/hrefFunctionTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPageInEnglish_givenPageWithResourceBundleMessage_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"messageEnTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/messageEnTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPageInFrench_givenPageWithResourceBundleMessage_thenRenderingIsOK()\n      throws Exception {\n    this.mockRequest.addPreferredLocale(Locale.CANADA_FRENCH);\n\n    String result = this.render(\"messageFrTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/messageFrTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHttpRequestVariable_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"requestTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/requestTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHttpResponseVariable_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"responseTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/responseTest.html\");\n  }\n\n  @Test\n  void whenRenderingAPage_givenPageWithHttpSessionVariable_thenRenderingIsOK()\n      throws Exception {\n    String result = this.render(\"sessionTest\", new HashMap<String, Object>());\n\n    this.assertOutput(result, EXPECTED_RESPONSE_PATH + \"/sessionTest.html\");\n  }\n\n  private void assertOutput(String output, String expectedOutput) throws IOException {\n    assertEquals(this.readExpectedOutputResource(expectedOutput), output.replaceAll(\"\\\\s\", \"\"));\n  }\n\n  private String readExpectedOutputResource(String expectedOutput) throws IOException {\n    BufferedReader reader = new BufferedReader(\n        new InputStreamReader(this.getClass().getResourceAsStream(expectedOutput)));\n\n    StringBuilder builder = new StringBuilder();\n    for (String currentLine = reader.readLine(); currentLine != null;\n        currentLine = reader.readLine()) {\n      builder.append(currentLine);\n    }\n\n    return this.removeAllWhitespaces(builder.toString());\n  }\n\n  private String removeAllWhitespaces(String source) {\n    return source.replaceAll(\"\\\\s\", \"\");\n  }\n\n  private String render(String location, Map<String, ?> model) throws Exception {\n    this.viewResolver.resolveViewName(location, DEFAULT_LOCALE)\n        .render(model, this.mockRequest, this.mockResponse);\n    return this.mockResponse.getContentAsString();\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/java/io/pebbletemplates/spring/bean/SomeBean.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.bean;\n\n/**\n * Spring bean for unit test\n *\n * @author Eric Bussieres\n */\npublic class SomeBean {\n\n  public String foo() {\n    return \"foo\";\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/java/io/pebbletemplates/spring/config/MVCConfig.java",
    "content": "/*\n * Copyright (c) 2013 by Mitchell Bösecke\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\npackage io.pebbletemplates.spring.config;\n\nimport io.pebbletemplates.pebble.PebbleEngine;\nimport io.pebbletemplates.pebble.loader.ClasspathLoader;\nimport io.pebbletemplates.pebble.loader.Loader;\nimport io.pebbletemplates.spring.bean.SomeBean;\nimport io.pebbletemplates.spring.extension.SpringExtension;\nimport io.pebbletemplates.spring.servlet.PebbleViewResolver;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.support.ResourceBundleMessageSource;\nimport org.springframework.web.servlet.ViewResolver;\n\n/**\n * Spring configuration for unit test\n *\n * @author Eric Bussieres\n */\n@Configuration(proxyBeanMethods = false)\npublic class MVCConfig {\n\n  @Bean\n  public SomeBean foo() {\n    return new SomeBean();\n  }\n\n  @Bean\n  public MessageSource messageSource() {\n    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();\n    messageSource.setBasename(\"io.pebbletemplates.spring.messages\");\n\n    return messageSource;\n  }\n\n  @Bean\n  public PebbleEngine pebbleEngine(SpringExtension springExtension,\n                                   Loader<?> templateLoader) {\n    return new PebbleEngine.Builder()\n        .loader(templateLoader)\n        .strictVariables(false)\n        .extension(springExtension)\n        .build();\n  }\n\n  @Bean\n  public SpringExtension springExtension(MessageSource messageSource) {\n    return new SpringExtension(messageSource);\n  }\n\n  @Bean\n  public Loader<?> templateLoader() {\n    return new ClasspathLoader();\n  }\n\n  @Bean\n  public ViewResolver viewResolver(PebbleEngine pebbleEngine) {\n    PebbleViewResolver viewResolver = new PebbleViewResolver(pebbleEngine);\n    viewResolver.setPrefix(\"io/pebbletemplates/spring/template/\");\n    viewResolver.setSuffix(\".html\");\n    viewResolver.setContentType(\"text/html\");\n    return viewResolver;\n  }\n}\n"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/beansTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>foo</title>\n</head>\n\n<body>\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/bindingResultTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\ntrue\ntrue\ntrue\n\nfalse\nfalse\nfalse\n\nSome error\nSome global error with params param1 and param2\nSome field error with params param1 and param2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/bindingResultWithMacroTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\ntrue\ntrue\ntrue\n\nfalse\nfalse\nfalse\n\nSome error\nSome global error with params param1 and param2\nSome field error with params param1 and param2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/hrefFunctionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nHrefFunction string interpolation = /testContextPath/foo\nHrefFunction static = /testContextPath/foobar\nHrefFunction expression = /testContextPath/foo\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/messageEnTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = Some label\nLabel with params = Some label with params params1 and params2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/messageFrTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = Un libellé\nLabel with params = Un libellé avec les paramètres params1 et params2\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/requestTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nContext path = /testContextPath\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/responseTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nResponse contentType = text/html;charset=UTF-8\nResponse character encoding = UTF-8\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/expectedResponse/sessionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nSession maxInactiveInterval = 600\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/messages_en.properties",
    "content": "label.test=Some label\nlabel.test.params=Some label with params {0} and {1}\nerror.test=Some error\nerror.global.test.params=Some global error with params {0} and {1}\nerror.field.test.params=Some field error with params {0} and {1}"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/messages_fr.properties",
    "content": "label.test=Un libell\nlabel.test.params=Un libell avec les paramtres {0} et {1}\nerror.test=Une erreur\nerror.global.test.params=Une erreur globale avec les paramtres {0} et {1}\nerror.field.test.params=Une erreur sur un champ avec les paramtres {0} et {1}"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/beansTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>{{ beans.foo.foo() }}</title>\n</head>\n\n<body>\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/bindingResultTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\n{{ hasErrors('formName') }}\n{{ hasGlobalErrors('formName') }}\n{{ hasFieldErrors('formName', 'testField') }}\n\n{{ hasErrors('') }}\n{{ hasGlobalErrors('') }}\n{{ hasFieldErrors('formName', '') }}\n\n{% for err in getAllErrors('formName') %}{{ err }}{% endfor %}\n{% for err in getGlobalErrors('formName') %}{{ err }}{% endfor %}\n{% for err in getFieldErrors('formName', 'testField') %}{{ err }}{% endfor %}\n\n{% for err in getAllErrors('') %}{{ err }}{% endfor %}\n{% for err in getGlobalErrors('') %}{{ err }}{% endfor %}\n{% for err in getFieldErrors('', 'testField') %}{{ err }}{% endfor %}\n{% for err in getFieldErrors('formName', '') %}{{ err }}{% endfor %}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/bindingResultWithMacroTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\n{{ test(_context) }}\n{% macro test(_context) %}\n  {{ hasErrors('formName') }}\n  {{ hasGlobalErrors('formName') }}\n  {{ hasFieldErrors('formName', 'testField') }}\n\n  {{ hasErrors('') }}\n  {{ hasGlobalErrors('') }}\n  {{ hasFieldErrors('formName', '') }}\n\n  {% for err in getAllErrors('formName') %}{{ err }}{% endfor %}\n  {% for err in getGlobalErrors('formName') %}{{ err }}{% endfor %}\n  {% for err in getFieldErrors('formName', 'testField') %}{{ err }}{% endfor %}\n\n  {% for err in getAllErrors('') %}{{ err }}{% endfor %}\n  {% for err in getGlobalErrors('') %}{{ err }}{% endfor %}\n  {% for err in getFieldErrors('', 'testField') %}{{ err }}{% endfor %}\n  {% for err in getFieldErrors('formName', '') %}{{ err }}{% endfor %}\n{% endmacro %}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/hrefFunctionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nHrefFunction string interpolation = {{ href(\"/#{beans.foo.foo}\") }}\nHrefFunction static = {{ href('/foobar') }}\nHrefFunction expression = {{ href('/' + beans.foo.foo) }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/messageEnTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = {{ message('label.test') }}\nLabel with params = {{ message('label.test.params', 'params1', 'params2') }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/messageFrTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nLabel = {{ message('label.test') }}\nLabel with params = {{ message('label.test.params', 'params1', 'params2') }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/requestTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nContext path = {{ request.contextPath }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/responseTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nResponse contentType = {{ response.contentType }}\nResponse character encoding = {{ response.characterEncoding }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pebble-spring7/src/test/resources/io/pebbletemplates/spring/template/sessionTest.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n  <title>test</title>\n</head>\n\n<body>\nSession maxInactiveInterval = {{ session.maxInactiveInterval }}\n</body>\n</html>"
  },
  {
    "path": "pebble-spring/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>io.pebbletemplates</groupId>\n    <artifactId>pebble-project</artifactId>\n    <version>4.1.2-SNAPSHOT</version>\n  </parent>\n\n  <artifactId>pebble-spring</artifactId>\n  <packaging>pom</packaging>\n\n  <modules>\n    <module>pebble-spring6</module>\n    <module>pebble-legacy-spring-boot-starter</module>\n    <module>pebble-spring7</module>\n    <module>pebble-spring-boot-starter</module>\n  </modules>\n\n  <name>Pebble Spring Project</name>\n  <description>Pebble Spring Project</description>\n  <url>http://pebbletemplates.io</url>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>io.pebbletemplates</groupId>\n        <artifactId>pebble</artifactId>\n        <version>${project.version}</version>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n</project>\n\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>io.pebbletemplates</groupId>\n  <artifactId>pebble-project</artifactId>\n  <version>4.1.2-SNAPSHOT</version>\n\n  <packaging>pom</packaging>\n\n  <modules>\n    <module>pebble</module>\n    <module>pebble-spring</module>\n    <module>docs</module>\n  </modules>\n\n  <name>Pebble Project</name>\n  <description>Pebble Project</description>\n  <url>http://pebbletemplates.io</url>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <java.version>1.8</java.version>\n  </properties>\n\n  <licenses>\n    <license>\n      <name>BSD 3-Clause License</name>\n      <url>http://opensource.org/licenses/BSD-3-Clause</url>\n      <distribution>repo</distribution>\n    </license>\n  </licenses>\n\n  <developers>\n    <developer>\n      <id>mitchellbosecke</id>\n      <name>Mitchell Bösecke</name>\n      <email>mitchellbosecke@gmail.com</email>\n    </developer>\n    <developer>\n      <id>ebussieres</id>\n      <name>Eric Bussieres</name>\n      <email>erbussieres AT gmail DOT com</email>\n      <roles>\n        <role>Developer</role>\n      </roles>\n    </developer>\n  </developers>\n\n  <build>\n    <plugins>\n      <plugin>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.15.0</version>\n        <configuration>\n          <source>${java.version}</source>\n          <target>${java.version}</target>\n          <parameters>true</parameters>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <version>3.5.0</version>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <version>3.5.5</version>\n        <configuration>\n          <argLine>-Dfile.encoding=${project.build.sourceEncoding}</argLine>\n          <statelessTestsetReporter>\n            <disable>true</disable>\n          </statelessTestsetReporter>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.sonatype.central</groupId>\n        <artifactId>central-publishing-maven-plugin</artifactId>\n        <version>0.10.0</version>\n        <extensions>true</extensions>\n        <configuration>\n          <publishingServerId>central</publishingServerId>\n          <autoPublish>true</autoPublish>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-release-plugin</artifactId>\n        <version>3.3.1</version>\n        <configuration>\n          <tagNameFormat>v@{project.version}</tagNameFormat>\n          <autoVersionSubmodules>true</autoVersionSubmodules>\n          <useReleaseProfile>false</useReleaseProfile>\n          <releaseProfiles>release</releaseProfiles>\n          <goals>deploy</goals>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <profile>\n      <id>release</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-source-plugin</artifactId>\n            <version>3.4.0</version>\n            <executions>\n              <execution>\n                <id>attach-sources</id>\n                <goals>\n                  <goal>jar</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-javadoc-plugin</artifactId>\n            <version>3.12.0</version>\n            <executions>\n              <execution>\n                <id>attach-javadocs</id>\n                <goals>\n                  <goal>jar</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-gpg-plugin</artifactId>\n            <version>3.2.8</version>\n            <executions>\n              <execution>\n                <id>sign-artifacts</id>\n                <phase>verify</phase>\n                <goals>\n                  <goal>sign</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n\n  <distributionManagement>\n    <snapshotRepository>\n      <id>ossrh</id>\n      <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n    </snapshotRepository>\n  </distributionManagement>\n\n  <repositories>\n    <repository>\n      <id>spring-milestones</id>\n      <name>Spring Milestones</name>\n      <url>https://repo.spring.io/milestone</url>\n    </repository>\n  </repositories>\n  <pluginRepositories>\n    <pluginRepository>\n      <id>spring-milestones</id>\n      <name>Spring Milestones</name>\n      <url>https://repo.spring.io/milestone</url>\n    </pluginRepository>\n  </pluginRepositories>\n\n  <scm>\n    <connection>scm:git:git://github.com/PebbleTemplates/pebble.git</connection>\n    <developerConnection>scm:git:git@github.com:PebbleTemplates/pebble.git</developerConnection>\n    <url>https://github.com/PebbleTemplates/pebble.git</url>\n    <tag>HEAD</tag>\n  </scm>\n</project>\n\n"
  }
]