Repository: mbosecke/pebble
Branch: master
Commit: 37f69de6df9f
Files: 788
Total size: 1.4 MB
Directory structure:
gitextract_382czipv/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── config.yml
│ │ └── issue.md
│ ├── dependabot.yml
│ └── workflows/
│ └── maven.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docs/
│ ├── pom.xml
│ └── src/
│ └── orchid/
│ └── resources/
│ ├── assets/
│ │ └── css/
│ │ └── github-fork-ribbon.css
│ ├── changelog/
│ │ ├── v3_0_0.md
│ │ ├── v3_0_1.md
│ │ ├── v3_0_10.md
│ │ ├── v3_0_2.md
│ │ ├── v3_0_3.md
│ │ ├── v3_0_4.md
│ │ ├── v3_0_5.md
│ │ ├── v3_0_6.md
│ │ ├── v3_0_7.md
│ │ ├── v3_0_8.md
│ │ ├── v3_0_9.md
│ │ ├── v3_1_0.md
│ │ ├── v3_1_1.md
│ │ ├── v3_1_2.md
│ │ ├── v3_1_3.md
│ │ ├── v3_1_4.md
│ │ ├── v3_1_5.md
│ │ ├── v3_1_6.md
│ │ ├── v3_2_0.md
│ │ ├── v3_2_1.md
│ │ ├── v3_2_2.md
│ │ ├── v3_2_3.md
│ │ ├── v3_2_4.md
│ │ ├── v4_0_0.md
│ │ ├── v4_1_0.md
│ │ └── v4_1_1.md
│ ├── config.yml
│ ├── data/
│ │ ├── contributors.yml
│ │ └── twig-compatibility/
│ │ ├── filters.yml
│ │ ├── functions.yml
│ │ ├── operators.yml
│ │ ├── tags.yml
│ │ └── tests.yml
│ ├── homepage.md
│ ├── pages/
│ │ ├── CNAME
│ │ ├── changelog.md
│ │ ├── contributing.md
│ │ └── twig-compatibility.peb
│ ├── templates/
│ │ ├── includes/
│ │ │ └── sidebar.peb
│ │ └── layouts/
│ │ └── index.peb
│ └── wiki/
│ ├── filter/
│ │ ├── abbreviate.md
│ │ ├── abs.md
│ │ ├── base64decode.md
│ │ ├── base64encode.md
│ │ ├── capitalize.md
│ │ ├── date.md
│ │ ├── default.md
│ │ ├── escape.md
│ │ ├── first.md
│ │ ├── format.md
│ │ ├── join.md
│ │ ├── last.md
│ │ ├── length.md
│ │ ├── lower.md
│ │ ├── nl2br.md
│ │ ├── numberformat.md
│ │ ├── raw.md
│ │ ├── replace.md
│ │ ├── reverse.md
│ │ ├── rsort.md
│ │ ├── sha256.md
│ │ ├── slice.md
│ │ ├── sort.md
│ │ ├── split.md
│ │ ├── title.md
│ │ ├── trim.md
│ │ ├── upper.md
│ │ └── urlencode.md
│ ├── function/
│ │ ├── blockFunction.md
│ │ ├── i18n.md
│ │ ├── max.md
│ │ ├── min.md
│ │ ├── parent.md
│ │ └── range.md
│ ├── guide/
│ │ ├── basic-usage.md
│ │ ├── customize-defaults.md
│ │ ├── escaping.md
│ │ ├── extending-pebble.md
│ │ ├── high-performance.md
│ │ ├── installation.md
│ │ └── spring-boot-integration.md
│ ├── operator/
│ │ ├── comparisons.md
│ │ ├── contains.md
│ │ ├── is.md
│ │ ├── logic.md
│ │ ├── math.md
│ │ └── others.md
│ ├── summary.md
│ ├── tag/
│ │ ├── autoescape.md
│ │ ├── block.md
│ │ ├── cache.md
│ │ ├── embed.md
│ │ ├── extends.md
│ │ ├── filter.md
│ │ ├── flush.md
│ │ ├── for.md
│ │ ├── from.md
│ │ ├── if.md
│ │ ├── import.md
│ │ ├── include.md
│ │ ├── macro.md
│ │ ├── parallel.md
│ │ ├── set.md
│ │ └── verbatim.md
│ └── test/
│ ├── empty.md
│ ├── even.md
│ ├── iterable.md
│ ├── map.md
│ ├── null.md
│ └── odd.md
├── intellij-java-google-style.xml
├── pebble/
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── io/
│ │ └── pebbletemplates/
│ │ └── pebble/
│ │ ├── PebbleEngine.java
│ │ ├── attributes/
│ │ │ ├── ArrayResolver.java
│ │ │ ├── AttributeResolver.java
│ │ │ ├── DefaultAttributeResolver.java
│ │ │ ├── ListResolver.java
│ │ │ ├── MacroResolver.java
│ │ │ ├── MapResolver.java
│ │ │ ├── MemberCacheUtils.java
│ │ │ ├── ResolvedAttribute.java
│ │ │ └── methodaccess/
│ │ │ ├── BlacklistMethodAccessValidator.java
│ │ │ ├── MethodAccessValidator.java
│ │ │ └── NoOpMethodAccessValidator.java
│ │ ├── cache/
│ │ │ ├── CacheKey.java
│ │ │ ├── PebbleCache.java
│ │ │ ├── tag/
│ │ │ │ ├── CaffeineTagCache.java
│ │ │ │ ├── ConcurrentMapTagCache.java
│ │ │ │ └── NoOpTagCache.java
│ │ │ └── template/
│ │ │ ├── CaffeineTemplateCache.java
│ │ │ ├── ConcurrentMapTemplateCache.java
│ │ │ └── NoOpTemplateCache.java
│ │ ├── error/
│ │ │ ├── AttributeNotFoundException.java
│ │ │ ├── ClassAccessException.java
│ │ │ ├── LoaderException.java
│ │ │ ├── ParserException.java
│ │ │ ├── PebbleException.java
│ │ │ └── RootAttributeNotFoundException.java
│ │ ├── extension/
│ │ │ ├── AbstractExtension.java
│ │ │ ├── AbstractNodeVisitor.java
│ │ │ ├── Extension.java
│ │ │ ├── ExtensionCustomizer.java
│ │ │ ├── ExtensionRegistry.java
│ │ │ ├── ExtensionRegistryFactory.java
│ │ │ ├── Filter.java
│ │ │ ├── Function.java
│ │ │ ├── NamedArguments.java
│ │ │ ├── NodeVisitor.java
│ │ │ ├── NodeVisitorFactory.java
│ │ │ ├── Test.java
│ │ │ ├── core/
│ │ │ │ ├── AbbreviateFilter.java
│ │ │ │ ├── AbsFilter.java
│ │ │ │ ├── AttributeResolverExtension.java
│ │ │ │ ├── Base64DecoderFilter.java
│ │ │ │ ├── Base64EncoderFilter.java
│ │ │ │ ├── CapitalizeFilter.java
│ │ │ │ ├── CoreExtension.java
│ │ │ │ ├── DateFilter.java
│ │ │ │ ├── DefaultFilter.java
│ │ │ │ ├── DefinedTest.java
│ │ │ │ ├── DisallowExtensionCustomizerBuilder.java
│ │ │ │ ├── EmptyTest.java
│ │ │ │ ├── EvenTest.java
│ │ │ │ ├── FirstFilter.java
│ │ │ │ ├── FormatFilter.java
│ │ │ │ ├── IterableTest.java
│ │ │ │ ├── JoinFilter.java
│ │ │ │ ├── LastFilter.java
│ │ │ │ ├── LengthFilter.java
│ │ │ │ ├── LowerFilter.java
│ │ │ │ ├── MacroAndBlockRegistrantNodeVisitor.java
│ │ │ │ ├── MacroAndBlockRegistrantNodeVisitorFactory.java
│ │ │ │ ├── MapTest.java
│ │ │ │ ├── MaxFunction.java
│ │ │ │ ├── MergeFilter.java
│ │ │ │ ├── MinFunction.java
│ │ │ │ ├── Nl2brFilter.java
│ │ │ │ ├── NullTest.java
│ │ │ │ ├── NumberFormatFilter.java
│ │ │ │ ├── OddTest.java
│ │ │ │ ├── RangeFunction.java
│ │ │ │ ├── ReplaceFilter.java
│ │ │ │ ├── ReverseFilter.java
│ │ │ │ ├── RsortFilter.java
│ │ │ │ ├── Sha256Filter.java
│ │ │ │ ├── SliceFilter.java
│ │ │ │ ├── SortFilter.java
│ │ │ │ ├── SplitFilter.java
│ │ │ │ ├── TitleFilter.java
│ │ │ │ ├── TrimFilter.java
│ │ │ │ ├── UpperFilter.java
│ │ │ │ └── UrlEncoderFilter.java
│ │ │ ├── debug/
│ │ │ │ ├── DebugExtension.java
│ │ │ │ ├── PrettyPrintNodeVisitor.java
│ │ │ │ └── PrettyPrintNodeVisitorFactory.java
│ │ │ ├── escaper/
│ │ │ │ ├── EscapeFilter.java
│ │ │ │ ├── EscaperExtension.java
│ │ │ │ ├── EscaperNodeVisitor.java
│ │ │ │ ├── EscaperNodeVisitorFactory.java
│ │ │ │ ├── EscapingStrategy.java
│ │ │ │ ├── RawFilter.java
│ │ │ │ └── SafeString.java
│ │ │ ├── i18n/
│ │ │ │ ├── I18nExtension.java
│ │ │ │ ├── UTF8Control.java
│ │ │ │ └── i18nFunction.java
│ │ │ └── writer/
│ │ │ ├── PooledSpecializedStringWriter.java
│ │ │ ├── SpecializedWriter.java
│ │ │ └── StringWriterSpecializedAdapter.java
│ │ ├── lexer/
│ │ │ ├── Lexer.java
│ │ │ ├── LexerImpl.java
│ │ │ ├── Syntax.java
│ │ │ ├── TemplateSource.java
│ │ │ ├── Token.java
│ │ │ └── TokenStream.java
│ │ ├── loader/
│ │ │ ├── AbstractServletLoader.java
│ │ │ ├── ClasspathLoader.java
│ │ │ ├── DelegatingLoader.java
│ │ │ ├── DelegatingLoaderCacheKey.java
│ │ │ ├── FileLoader.java
│ │ │ ├── Loader.java
│ │ │ ├── MemoryLoader.java
│ │ │ ├── Servlet5Loader.java
│ │ │ ├── ServletLoader.java
│ │ │ └── StringLoader.java
│ │ ├── node/
│ │ │ ├── AbstractRenderableNode.java
│ │ │ ├── ArgumentsNode.java
│ │ │ ├── AutoEscapeNode.java
│ │ │ ├── BlockNode.java
│ │ │ ├── BodyNode.java
│ │ │ ├── CacheNode.java
│ │ │ ├── EmbedNode.java
│ │ │ ├── ExtendsNode.java
│ │ │ ├── FlushNode.java
│ │ │ ├── ForNode.java
│ │ │ ├── FromNode.java
│ │ │ ├── FunctionOrMacroNameNode.java
│ │ │ ├── IfNode.java
│ │ │ ├── ImportNode.java
│ │ │ ├── IncludeNode.java
│ │ │ ├── MacroNode.java
│ │ │ ├── NamedArgumentNode.java
│ │ │ ├── Node.java
│ │ │ ├── ParallelNode.java
│ │ │ ├── PositionalArgumentNode.java
│ │ │ ├── PrintNode.java
│ │ │ ├── RenderableNode.java
│ │ │ ├── RootNode.java
│ │ │ ├── SetNode.java
│ │ │ ├── TestInvocationExpression.java
│ │ │ ├── TextNode.java
│ │ │ ├── expression/
│ │ │ │ ├── AddExpression.java
│ │ │ │ ├── AndExpression.java
│ │ │ │ ├── ArrayExpression.java
│ │ │ │ ├── BinaryExpression.java
│ │ │ │ ├── BlockFunctionExpression.java
│ │ │ │ ├── ConcatenateExpression.java
│ │ │ │ ├── ContainsExpression.java
│ │ │ │ ├── ContextVariableExpression.java
│ │ │ │ ├── DivideExpression.java
│ │ │ │ ├── EqualsExpression.java
│ │ │ │ ├── Expression.java
│ │ │ │ ├── FilterExpression.java
│ │ │ │ ├── FilterInvocationExpression.java
│ │ │ │ ├── FunctionOrMacroInvocationExpression.java
│ │ │ │ ├── GetAttributeExpression.java
│ │ │ │ ├── GreaterThanEqualsExpression.java
│ │ │ │ ├── GreaterThanExpression.java
│ │ │ │ ├── LessThanEqualsExpression.java
│ │ │ │ ├── LessThanExpression.java
│ │ │ │ ├── LiteralBigDecimalExpression.java
│ │ │ │ ├── LiteralBooleanExpression.java
│ │ │ │ ├── LiteralDoubleExpression.java
│ │ │ │ ├── LiteralIntegerExpression.java
│ │ │ │ ├── LiteralLongExpression.java
│ │ │ │ ├── LiteralNullExpression.java
│ │ │ │ ├── LiteralStringExpression.java
│ │ │ │ ├── MapExpression.java
│ │ │ │ ├── ModulusExpression.java
│ │ │ │ ├── MultiplyExpression.java
│ │ │ │ ├── NegativeTestExpression.java
│ │ │ │ ├── NotEqualsExpression.java
│ │ │ │ ├── OrExpression.java
│ │ │ │ ├── ParentFunctionExpression.java
│ │ │ │ ├── PositiveTestExpression.java
│ │ │ │ ├── RangeExpression.java
│ │ │ │ ├── RenderableNodeExpression.java
│ │ │ │ ├── SubtractExpression.java
│ │ │ │ ├── TernaryExpression.java
│ │ │ │ ├── UnaryExpression.java
│ │ │ │ ├── UnaryMinusExpression.java
│ │ │ │ ├── UnaryNotExpression.java
│ │ │ │ └── UnaryPlusExpression.java
│ │ │ └── fornode/
│ │ │ ├── LazyLength.java
│ │ │ └── LazyRevIndex.java
│ │ ├── operator/
│ │ │ ├── Associativity.java
│ │ │ ├── BinaryOperator.java
│ │ │ ├── BinaryOperatorImpl.java
│ │ │ ├── BinaryOperatorType.java
│ │ │ ├── UnaryOperator.java
│ │ │ └── UnaryOperatorImpl.java
│ │ ├── parser/
│ │ │ ├── ExpressionParser.java
│ │ │ ├── Parser.java
│ │ │ ├── ParserImpl.java
│ │ │ ├── ParserOptions.java
│ │ │ └── StoppingCondition.java
│ │ ├── template/
│ │ │ ├── Block.java
│ │ │ ├── EvaluationContext.java
│ │ │ ├── EvaluationContextImpl.java
│ │ │ ├── EvaluationOptions.java
│ │ │ ├── GlobalContext.java
│ │ │ ├── Hierarchy.java
│ │ │ ├── Macro.java
│ │ │ ├── MacroAttributeProvider.java
│ │ │ ├── PebbleTemplate.java
│ │ │ ├── PebbleTemplateImpl.java
│ │ │ ├── RenderedSizeContext.java
│ │ │ ├── Scope.java
│ │ │ └── ScopeChain.java
│ │ ├── tokenParser/
│ │ │ ├── AutoEscapeTokenParser.java
│ │ │ ├── BlockTokenParser.java
│ │ │ ├── CacheTokenParser.java
│ │ │ ├── EmbedTokenParser.java
│ │ │ ├── ExtendsTokenParser.java
│ │ │ ├── FilterTokenParser.java
│ │ │ ├── FlushTokenParser.java
│ │ │ ├── ForTokenParser.java
│ │ │ ├── FromTokenParser.java
│ │ │ ├── IfTokenParser.java
│ │ │ ├── ImportTokenParser.java
│ │ │ ├── IncludeTokenParser.java
│ │ │ ├── MacroTokenParser.java
│ │ │ ├── ParallelTokenParser.java
│ │ │ ├── SetTokenParser.java
│ │ │ ├── TokenParser.java
│ │ │ └── VerbatimTokenParser.java
│ │ └── utils/
│ │ ├── Callbacks.java
│ │ ├── FutureWriter.java
│ │ ├── LimitedSizeWriter.java
│ │ ├── OperatorUtils.java
│ │ ├── Pair.java
│ │ ├── PathUtils.java
│ │ ├── StringLengthComparator.java
│ │ ├── StringUtils.java
│ │ └── TypeUtils.java
│ └── test/
│ ├── java/
│ │ └── io/
│ │ └── pebbletemplates/
│ │ └── pebble/
│ │ ├── ArgumentsNodeTest.java
│ │ ├── ArraySyntaxTest.java
│ │ ├── AttributeSubscriptSyntaxTest.java
│ │ ├── CacheTest.java
│ │ ├── CompilerTest.java
│ │ ├── ConcurrencyTest.java
│ │ ├── ContextTest.java
│ │ ├── CoreFiltersTest.java
│ │ ├── CoreFunctionsTest.java
│ │ ├── CoreTagsTest.java
│ │ ├── CoreTestsTest.java
│ │ ├── DynamicNamedArgsTest.java
│ │ ├── EmbedCachingTagTest.java
│ │ ├── EmbedTagTest.java
│ │ ├── EnumEqualsTest.java
│ │ ├── ErrorReportingTest.java
│ │ ├── EscaperExtensionTest.java
│ │ ├── ExtendingPebbleTest.java
│ │ ├── FileLoaderTest.java
│ │ ├── ForTest.java
│ │ ├── GetAttributeTest.java
│ │ ├── I18nExtensionTest.java
│ │ ├── IncludeWithParameterTest.java
│ │ ├── InheritanceTest.java
│ │ ├── LoaderTest.java
│ │ ├── LogicTest.java
│ │ ├── MacroTest.java
│ │ ├── MapSyntaxTest.java
│ │ ├── MaxRenderedSizeTest.java
│ │ ├── MethodAccessTemplateTest.java
│ │ ├── NewlineTrimmingTest.java
│ │ ├── Nl2brFilterTest.java
│ │ ├── OverloadedMethodTest.java
│ │ ├── OverrideCoreExtensionTest.java
│ │ ├── ParsingOdditiesTest.java
│ │ ├── RenderSingleBlockTest.java
│ │ ├── RenderWithoutEndBlockTest.java
│ │ ├── ScopeChainTest.java
│ │ ├── ScopeTest.java
│ │ ├── SplitFilterTest.java
│ │ ├── StrictModeTest.java
│ │ ├── StringInterpolationTest.java
│ │ ├── TernaryExpressionTest.java
│ │ ├── TestParallelParsing.java
│ │ ├── TestRelativePath.java
│ │ ├── WhitespaceControlTest.java
│ │ ├── WritingTest.java
│ │ ├── attributes/
│ │ │ └── methodaccess/
│ │ │ ├── BlacklistMethodAccessValidatorTest.java
│ │ │ ├── Foo.java
│ │ │ ├── InstanceProvider.java
│ │ │ ├── MethodsProvider.java
│ │ │ └── NoOpMethodAccessValidatorTest.java
│ │ ├── extension/
│ │ │ ├── ArrayToStringFilter.java
│ │ │ ├── ExtensionCustomizerTest.java
│ │ │ ├── InvocationCountingFunction.java
│ │ │ ├── ListToStringFilter.java
│ │ │ ├── MapToStringFilter.java
│ │ │ ├── TestingExtension.java
│ │ │ ├── core/
│ │ │ │ └── FormatFilterTest.java
│ │ │ └── escaper/
│ │ │ └── RawFilterTest.java
│ │ ├── lexer/
│ │ │ ├── IdentifierTest.java
│ │ │ ├── LexerImplTest.java
│ │ │ └── SyntaxTest.java
│ │ ├── macro/
│ │ │ ├── MacroGlobalVariablesTest.java
│ │ │ ├── PebbleExtension.java
│ │ │ ├── TestFilter.java
│ │ │ └── TestMacroCalls.java
│ │ ├── node/
│ │ │ ├── ForNodeTest.java
│ │ │ ├── IfNodeTest.java
│ │ │ └── expression/
│ │ │ ├── AndExpressionTest.java
│ │ │ ├── ExpressionTest.java
│ │ │ ├── OrExpressionTest.java
│ │ │ ├── StringExpressionParserTest.java
│ │ │ └── UnaryNotExpressionTest.java
│ │ ├── template/
│ │ │ └── tests/
│ │ │ ├── PebbleTestContext.java
│ │ │ ├── WhiteSpaceControlWithNewLineTrimmingTests.java
│ │ │ └── input/
│ │ │ ├── PebbleTestItem.java
│ │ │ └── PebbleTestItemType.java
│ │ └── utils/
│ │ ├── LimitedSizeWriterTest.java
│ │ ├── OperatorUtilsToNumberTest.java
│ │ └── PathUtilsTest.java
│ └── resources/
│ ├── logback-test.xml
│ ├── security/
│ │ ├── allowedMethods.properties
│ │ └── unsafeMethods.properties
│ ├── template-tests/
│ │ ├── DoubleNestedIfStatement.peb
│ │ ├── DoubleNestedIfStatement.txt
│ │ ├── ForLoopWithNestedIfStatementAndMacro.peb
│ │ ├── ForLoopWithNestedIfStatementAndMacro.txt
│ │ ├── NestedIfStatementWithOneElseIfStatements.peb
│ │ ├── NestedIfStatementWithOneElseIfStatements.txt
│ │ ├── NestedIfStatementWithThreeElseIfStatements.peb
│ │ ├── NestedIfStatementWithThreeElseIfStatements.txt
│ │ ├── NestedIfStatementWithTwoElseIfStatements.peb
│ │ └── NestedIfStatementWithTwoElseIfStatements.txt
│ ├── templates/
│ │ ├── cache/
│ │ │ ├── cache1/
│ │ │ │ └── template.cache.peb
│ │ │ ├── cache2/
│ │ │ │ └── template.cache.peb
│ │ │ ├── template.cacheChild.peb
│ │ │ └── template.cacheParent.peb
│ │ ├── embed/
│ │ │ ├── cache/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template1.peb
│ │ │ │ └── template2.peb
│ │ │ ├── notes.md
│ │ │ ├── test0/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test1/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test10/
│ │ │ │ ├── template.base.1.peb
│ │ │ │ ├── template.base.2.peb
│ │ │ │ ├── template.base.3.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test11/
│ │ │ │ ├── template.base.1.peb
│ │ │ │ ├── template.base.2.peb
│ │ │ │ ├── template.base.3.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test12/
│ │ │ │ ├── template.base.1.peb
│ │ │ │ ├── template.base.2.peb
│ │ │ │ ├── template.base.3.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test13/
│ │ │ │ ├── template.embedbase.1.peb
│ │ │ │ ├── template.embedbase.2.peb
│ │ │ │ ├── template.embedbase.3.peb
│ │ │ │ ├── template.extendsbase.1.peb
│ │ │ │ ├── template.extendsbase.2.peb
│ │ │ │ ├── template.extendsbase.3.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test14/
│ │ │ │ ├── template.embedbase.1.peb
│ │ │ │ ├── template.extendsbase.1.peb
│ │ │ │ ├── template.extendsbase.2.peb
│ │ │ │ ├── template.extendsbase.3.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test15/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.error.txt
│ │ │ │ └── template.peb
│ │ │ ├── test16/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.error.txt
│ │ │ │ └── template.peb
│ │ │ ├── test17/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.error.txt
│ │ │ │ └── template.peb
│ │ │ ├── test18/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.error.txt
│ │ │ │ └── template.peb
│ │ │ ├── test2/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.peb
│ │ │ │ └── template.result.txt
│ │ │ ├── test3/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.peb
│ │ │ │ └── template.result.txt
│ │ │ ├── test4/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test5/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test6/
│ │ │ │ ├── template.base.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test7/
│ │ │ │ ├── template.base.1.peb
│ │ │ │ ├── template.base.2.peb
│ │ │ │ ├── template.base.3.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ ├── test8/
│ │ │ │ ├── template.base.1.peb
│ │ │ │ ├── template.base.2.peb
│ │ │ │ ├── template.base.3.peb
│ │ │ │ ├── template.peb
│ │ │ │ ├── template.result.twig.txt
│ │ │ │ └── template.result.txt
│ │ │ └── test9/
│ │ │ ├── template.base.1.peb
│ │ │ ├── template.base.2.peb
│ │ │ ├── template.base.3.peb
│ │ │ ├── template.peb
│ │ │ ├── template.result.twig.txt
│ │ │ └── template.result.txt
│ │ ├── function/
│ │ │ ├── template.block.peb
│ │ │ ├── template.child.peb
│ │ │ ├── template.childThenParentThenMacro.peb
│ │ │ ├── template.childWithContext.peb
│ │ │ ├── template.parent.peb
│ │ │ ├── template.parentAccessContext.peb
│ │ │ ├── template.parentThenMacro.peb
│ │ │ └── template.subchild.peb
│ │ ├── loader/
│ │ │ └── template.loaderTest.peb
│ │ ├── macros/
│ │ │ ├── from.peb
│ │ │ ├── import.as.peb
│ │ │ ├── include.peb
│ │ │ ├── index.peb
│ │ │ ├── invalid.from.peb
│ │ │ ├── invalid.from.sameAlias.peb
│ │ │ ├── invalid.from.unknownMacro.peb
│ │ │ ├── invalid.import.as.sameAlias.peb
│ │ │ ├── invalid.macro.peb
│ │ │ ├── macro.peb
│ │ │ ├── setVariableBase.peb
│ │ │ └── setVariableMacro.peb
│ │ ├── relativepath/
│ │ │ ├── subdirectory1/
│ │ │ │ ├── template.backwardslashes.peb
│ │ │ │ └── template.forwardslashes.peb
│ │ │ ├── subdirectory2/
│ │ │ │ └── template.relativeinclude2.peb
│ │ │ ├── template.relativeextends1.peb
│ │ │ ├── template.relativeextends2.peb
│ │ │ ├── template.relativeimport1.peb
│ │ │ ├── template.relativeimport2.peb
│ │ │ ├── template.relativeinclude1.peb
│ │ │ └── template.relativeinclude2.peb
│ │ ├── single-block/
│ │ │ ├── template.renderextendedblock1.peb
│ │ │ └── template.renderextendedblock2.peb
│ │ ├── template.autoescapeInclude1.peb
│ │ ├── template.autoescapeInclude2.peb
│ │ ├── template.autoescapeParent1.peb
│ │ ├── template.autoescapeParent2.peb
│ │ ├── template.child.peb
│ │ ├── template.concurrent1.peb
│ │ ├── template.concurrent2.peb
│ │ ├── template.dynamicChild.peb
│ │ ├── template.dynamicParent1.peb
│ │ ├── template.dynamicParent2.peb
│ │ ├── template.errorReporting.peb
│ │ ├── template.escapeCharactersInText.peb
│ │ ├── template.general.peb
│ │ ├── template.grandfather.peb
│ │ ├── template.import.dynamic.macro_ajax.peb
│ │ ├── template.import.dynamic.macro_classic.peb
│ │ ├── template.import.dynamic.peb
│ │ ├── template.importWithinBlock.peb
│ │ ├── template.include.dynamic.adminFooter.peb
│ │ ├── template.include.dynamic.defaultFooter.peb
│ │ ├── template.include.dynamic.peb
│ │ ├── template.include1.peb
│ │ ├── template.include2.peb
│ │ ├── template.includeInheritance1.peb
│ │ ├── template.includeInheritance2.peb
│ │ ├── template.includeInheritance3.peb
│ │ ├── template.includeOverrideBlock.peb
│ │ ├── template.includeOverrideBlock2.peb
│ │ ├── template.includeOverrideVariable1.peb
│ │ ├── template.includeOverrideVariable2.peb
│ │ ├── template.includePropagatesContext.peb
│ │ ├── template.includePropagatesContext2.peb
│ │ ├── template.includeWithParameter1.peb
│ │ ├── template.includeWithParameter2.peb
│ │ ├── template.includeWithParameterNotIsolated1.peb
│ │ ├── template.includeWithParameterNotIsolated2.peb
│ │ ├── template.includeWithParameterObject1.peb
│ │ ├── template.includeWithParameterObject2.peb
│ │ ├── template.includeWithinBlock.peb
│ │ ├── template.loaderTest.peb
│ │ ├── template.loaderTest.peb.suffix
│ │ ├── template.macro.child.peb
│ │ ├── template.macro.exploding.peb
│ │ ├── template.macro.parent.peb
│ │ ├── template.macro1.peb
│ │ ├── template.macro2.peb
│ │ ├── template.macro3.peb
│ │ ├── template.macroDouble.peb
│ │ ├── template.parallelInclude1.peb
│ │ ├── template.parallelInclude2.peb
│ │ ├── template.parallelParsing1.peb
│ │ ├── template.parallelParsing2.peb
│ │ ├── template.parallelWithImport.peb
│ │ ├── template.parallelWithImport2.peb
│ │ ├── template.parent.peb
│ │ ├── template.parent2.peb
│ │ ├── template.set.child.peb
│ │ ├── template.set.parent.peb
│ │ ├── template.skipGenerationBlock1.peb
│ │ ├── template.skipGenerationBlock2.peb
│ │ ├── template.skipGenerationBlock3.peb
│ │ ├── template.skipGenerationMacro1.peb
│ │ ├── template.skipGenerationMacro2.peb
│ │ ├── template.skipGenerationMacro3.peb
│ │ ├── template.strictModeComplexExpression.peb
│ │ └── template.strictModeSimpleExpression.peb
│ ├── testMessages.properties
│ └── testMessages_es_US.properties
├── pebble-spring/
│ ├── pebble-legacy-spring-boot-starter/
│ │ ├── pom.xml
│ │ └── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── io/
│ │ │ │ └── pebbletemplates/
│ │ │ │ └── boot/
│ │ │ │ └── autoconfigure/
│ │ │ │ ├── AbstractPebbleConfiguration.java
│ │ │ │ ├── PebbleAutoConfiguration.java
│ │ │ │ ├── PebbleProperties.java
│ │ │ │ ├── PebbleReactiveWebConfiguration.java
│ │ │ │ ├── PebbleServletWebConfiguration.java
│ │ │ │ ├── PebbleTemplateAvailabilityProvider.java
│ │ │ │ └── package-info.java
│ │ │ └── resources/
│ │ │ └── META-INF/
│ │ │ ├── spring/
│ │ │ │ ├── aot.factories
│ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│ │ │ └── spring.factories
│ │ └── test/
│ │ ├── java/
│ │ │ └── io/
│ │ │ └── pebbletemplates/
│ │ │ └── boot/
│ │ │ ├── AppConfig.java
│ │ │ ├── Application.java
│ │ │ ├── Controllers.java
│ │ │ ├── Foo.java
│ │ │ └── autoconfigure/
│ │ │ ├── NonWebAppTests.java
│ │ │ ├── PebbleAutoConfigurationTest.java
│ │ │ ├── ReactiveAppTest.java
│ │ │ └── ServletAppTest.java
│ │ └── resources/
│ │ ├── messages.properties
│ │ ├── messages_es.properties
│ │ └── templates/
│ │ ├── beans.peb
│ │ ├── contextPath.peb
│ │ ├── extensions.peb
│ │ ├── hello.peb
│ │ ├── index.peb
│ │ ├── native-image.peb
│ │ ├── responseObject.peb
│ │ └── session.peb
│ ├── pebble-spring-boot-starter/
│ │ ├── pom.xml
│ │ └── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── io/
│ │ │ │ └── pebbletemplates/
│ │ │ │ └── boot/
│ │ │ │ └── autoconfigure/
│ │ │ │ ├── AbstractPebbleConfiguration.java
│ │ │ │ ├── PebbleAutoConfiguration.java
│ │ │ │ ├── PebbleProperties.java
│ │ │ │ ├── PebbleReactiveWebConfiguration.java
│ │ │ │ ├── PebbleServletWebConfiguration.java
│ │ │ │ ├── PebbleTemplateAvailabilityProvider.java
│ │ │ │ └── package-info.java
│ │ │ └── resources/
│ │ │ └── META-INF/
│ │ │ ├── spring/
│ │ │ │ ├── aot.factories
│ │ │ │ ├── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│ │ │ │ ├── org.springframework.boot.webflux.test.autoconfigure.AutoConfigureWebFlux.imports
│ │ │ │ └── org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc.imports
│ │ │ └── spring.factories
│ │ └── test/
│ │ ├── java/
│ │ │ └── io/
│ │ │ └── pebbletemplates/
│ │ │ └── boot/
│ │ │ ├── AppConfig.java
│ │ │ ├── Application.java
│ │ │ ├── Controllers.java
│ │ │ ├── Foo.java
│ │ │ └── autoconfigure/
│ │ │ ├── NonWebAppTests.java
│ │ │ ├── PebbleAutoConfigurationTest.java
│ │ │ ├── ReactiveAppTest.java
│ │ │ └── ServletAppTest.java
│ │ └── resources/
│ │ ├── messages.properties
│ │ ├── messages_es.properties
│ │ └── templates/
│ │ ├── beans.peb
│ │ ├── contextPath.peb
│ │ ├── extensions.peb
│ │ ├── hello.peb
│ │ ├── index.peb
│ │ ├── native-image.peb
│ │ ├── responseObject.peb
│ │ └── session.peb
│ ├── pebble-spring6/
│ │ ├── pom.xml
│ │ └── src/
│ │ ├── main/
│ │ │ └── java/
│ │ │ └── io/
│ │ │ └── pebbletemplates/
│ │ │ └── spring/
│ │ │ ├── context/
│ │ │ │ └── Beans.java
│ │ │ ├── extension/
│ │ │ │ ├── SpringExtension.java
│ │ │ │ └── function/
│ │ │ │ ├── HrefFunction.java
│ │ │ │ ├── MessageSourceFunction.java
│ │ │ │ └── bindingresult/
│ │ │ │ ├── BaseBindingResultFunction.java
│ │ │ │ ├── GetAllErrorsFunction.java
│ │ │ │ ├── GetFieldErrorsFunction.java
│ │ │ │ ├── GetGlobalErrorsFunction.java
│ │ │ │ ├── HasErrorsFunction.java
│ │ │ │ ├── HasFieldErrorsFunction.java
│ │ │ │ └── HasGlobalErrorsFunction.java
│ │ │ ├── reactive/
│ │ │ │ ├── PebbleReactiveView.java
│ │ │ │ └── PebbleReactiveViewResolver.java
│ │ │ └── servlet/
│ │ │ ├── PebbleView.java
│ │ │ └── PebbleViewResolver.java
│ │ └── test/
│ │ ├── java/
│ │ │ └── io/
│ │ │ └── pebbletemplates/
│ │ │ └── spring/
│ │ │ ├── PebbleViewResolverTest.java
│ │ │ ├── bean/
│ │ │ │ └── SomeBean.java
│ │ │ └── config/
│ │ │ └── MVCConfig.java
│ │ └── resources/
│ │ └── io/
│ │ └── pebbletemplates/
│ │ └── spring/
│ │ ├── expectedResponse/
│ │ │ ├── beansTest.html
│ │ │ ├── bindingResultTest.html
│ │ │ ├── bindingResultWithMacroTest.html
│ │ │ ├── hrefFunctionTest.html
│ │ │ ├── messageEnTest.html
│ │ │ ├── messageFrTest.html
│ │ │ ├── requestTest.html
│ │ │ ├── responseTest.html
│ │ │ └── sessionTest.html
│ │ ├── messages_en.properties
│ │ ├── messages_fr.properties
│ │ └── template/
│ │ ├── beansTest.html
│ │ ├── bindingResultTest.html
│ │ ├── bindingResultWithMacroTest.html
│ │ ├── hrefFunctionTest.html
│ │ ├── messageEnTest.html
│ │ ├── messageFrTest.html
│ │ ├── requestTest.html
│ │ ├── responseTest.html
│ │ └── sessionTest.html
│ ├── pebble-spring7/
│ │ ├── pom.xml
│ │ └── src/
│ │ ├── main/
│ │ │ └── java/
│ │ │ └── io/
│ │ │ └── pebbletemplates/
│ │ │ └── spring/
│ │ │ ├── context/
│ │ │ │ └── Beans.java
│ │ │ ├── extension/
│ │ │ │ ├── SpringExtension.java
│ │ │ │ └── function/
│ │ │ │ ├── HrefFunction.java
│ │ │ │ ├── MessageSourceFunction.java
│ │ │ │ └── bindingresult/
│ │ │ │ ├── BaseBindingResultFunction.java
│ │ │ │ ├── GetAllErrorsFunction.java
│ │ │ │ ├── GetFieldErrorsFunction.java
│ │ │ │ ├── GetGlobalErrorsFunction.java
│ │ │ │ ├── HasErrorsFunction.java
│ │ │ │ ├── HasFieldErrorsFunction.java
│ │ │ │ └── HasGlobalErrorsFunction.java
│ │ │ ├── reactive/
│ │ │ │ ├── PebbleReactiveView.java
│ │ │ │ └── PebbleReactiveViewResolver.java
│ │ │ └── servlet/
│ │ │ ├── PebbleView.java
│ │ │ └── PebbleViewResolver.java
│ │ └── test/
│ │ ├── java/
│ │ │ └── io/
│ │ │ └── pebbletemplates/
│ │ │ └── spring/
│ │ │ ├── PebbleViewResolverTest.java
│ │ │ ├── bean/
│ │ │ │ └── SomeBean.java
│ │ │ └── config/
│ │ │ └── MVCConfig.java
│ │ └── resources/
│ │ └── io/
│ │ └── pebbletemplates/
│ │ └── spring/
│ │ ├── expectedResponse/
│ │ │ ├── beansTest.html
│ │ │ ├── bindingResultTest.html
│ │ │ ├── bindingResultWithMacroTest.html
│ │ │ ├── hrefFunctionTest.html
│ │ │ ├── messageEnTest.html
│ │ │ ├── messageFrTest.html
│ │ │ ├── requestTest.html
│ │ │ ├── responseTest.html
│ │ │ └── sessionTest.html
│ │ ├── messages_en.properties
│ │ ├── messages_fr.properties
│ │ └── template/
│ │ ├── beansTest.html
│ │ ├── bindingResultTest.html
│ │ ├── bindingResultWithMacroTest.html
│ │ ├── hrefFunctionTest.html
│ │ ├── messageEnTest.html
│ │ ├── messageFrTest.html
│ │ ├── requestTest.html
│ │ ├── responseTest.html
│ │ └── sessionTest.html
│ └── pom.xml
└── pom.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
================================================
FILE: .github/FUNDING.yml
================================================
buy_me_a_coffee: erbussierel
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Community Support
url: https://github.com/PebbleTemplates/pebble/discussions
about: Please ask and answer questions here.
================================================
FILE: .github/ISSUE_TEMPLATE/issue.md
================================================
---
name: General
about: Bugs, enhancements, documentation, tasks.
title: ''
labels: ''
assignees: ''
---
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "maven"
directories:
- "/pebble"
- "/pebble-spring/pebble-spring7"
- "/pebble-spring/pebble-spring-boot-starter"
schedule:
interval: "daily"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
ignore:
- dependency-name: "javax.servlet:servlet-api"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
- dependency-name: "jakarta.servlet:jakarta.servlet-api"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
groups:
all-dependencies:
patterns:
- "*"
- package-ecosystem: "maven"
directories:
- "/pebble-spring/pebble-spring6"
- "/pebble-spring/pebble-legacy-spring-boot-starter"
schedule:
interval: "daily"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
ignore:
- dependency-name: "jakarta.servlet:jakarta.servlet-api"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
- dependency-name: "*"
update-types: ["version-update:semver-major"]
groups:
all-dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/workflows/maven.yml
================================================
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Build
on:
push:
branches: [ "*" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [ 17, 21, 25 ]
steps:
- uses: actions/checkout@v6
- name: Set up JDK
uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
#- name: Update dependency graph
# uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
================================================
FILE: .gitignore
================================================
*.class
# Package Files #
*.jar
*.war
*.ear
# Mac OS X
.DS_Store
# Eclipse #
.project
.classpath
.settings/
# IntelliJ
.idea/
*.iml
*.ipr
*.iws
target/
/target
/bin/
.gradle/
build/
*.releaseBackup
release.properties
================================================
FILE: LICENSE
================================================
Copyright (c) 2013, Mitchell Bösecke
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# Pebble [](https://buymeacoffee.com/erbussierel) [](https://github.com/PebbleTemplates/pebble/actions/workflows/maven.yml)
Pebble is a java templating engine inspired by [Twig](https://twig.symfony.com/). It separates itself from the crowd
with its inheritance feature and its easy-to-read syntax. It ships with built-in autoescaping for security, and it
includes integrated support for internationalization.
For more information please visit the [website](https://pebbletemplates.io).
# Breaking changes in version 4.1.x
- If you do not provide a custom Loader, Pebble will now use only a `ClasspathLoader` by default, same as the spring autoconfiguration.
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.
- Modify the `FileLoader` to use a mandatory base directory parameter.
# Breaking changes in version 4.0.x
- Use one of the following artifactId according to the spring boot version that you are using
| ArtifactId | spring-boot version |
|-----------------------------------|---------------------|
| pebble-legacy-spring-boot-starter | 3.x.x |
| pebble-spring-boot-starter | 4.x.x |
- The following spring boot properties has been moved to `.servlet` or `.reactive`
| Old property | New Property |
|---------------------------------|-----------------------------------------|
| pebble.allowRequestOverride | pebble.servlet.allowRequestOverride |
| pebble.allowSessionOverride | pebble.servlet.allowSessionOverride |
| pebble.cache | pebble.servlet.cache |
| pebble.contentType | pebble.servlet.contentType |
| pebble.exposeRequestAttributes | pebble.servlet.exposeRequestAttributes |
| pebble.exposeSessionAttributes | pebble.servlet.exposeSessionAttributes |
| pebble.exposeSpringMacroHelpers | pebble.servlet.exposeSpringMacroHelpers |
| | pebble.reactive.mediaTypes |
For more information, please consult the spring-boot integration documentation in
the [Boot externalized configuration section](https://pebbletemplates.io/wiki/guide/spring-boot-integration/)
# Breaking changes in version 3.2.x
- Rename package from `com.mitchellbosecke` to `io.pebbletemplates`
- Change default suffix to `.peb` instead of `.pebble` in spring boot autoconfiguration
- Rename method `getInstance` to `createInstance` in `BinaryOperator` interface (#521)
## License
Copyright (c) 2013, Mitchell Bösecke
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Reporting a Vulnerability
If 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.
================================================
FILE: docs/pom.xml
================================================
4.0.0io.pebbletemplatespebble-project4.1.2-SNAPSHOTdocspomPebble docsPebble documentationhttp://pebbletemplates.io0.17.4docio.github.javaeden.orchidorchid-maven-plugin${orchid.version}io.github.javaeden.orchidOrchidDocs${orchid.version}io.github.javaeden.orchidOrchidPluginDocs${orchid.version}io.github.javaeden.orchidOrchidGithub${orchid.version}Editorial${project.version}https://pebbletemplates.ioverifydeployjcenterhttps://jcenter.bintray.com kotlinxhttps://kotlin.bintray.com/kotlinx
================================================
FILE: docs/src/orchid/resources/assets/css/github-fork-ribbon.css
================================================
/*!
* "Fork me on GitHub" CSS ribbon v0.2.2 | MIT License
* https://github.com/simonwhitaker/github-fork-ribbon-css
*/
.github-fork-ribbon {
width: 12.1em;
height: 12.1em;
position: absolute;
overflow: hidden;
top: 0;
right: 0;
z-index: 9999;
pointer-events: none;
font-size: 13px;
text-decoration: none;
text-indent: -999999px;
border-bottom: none;
}
.github-fork-ribbon.fixed {
position: fixed;
}
.github-fork-ribbon:hover, .github-fork-ribbon:active {
background-color: rgba(0, 0, 0, 0.0);
}
.github-fork-ribbon:before, .github-fork-ribbon:after {
/* The right and left classes determine the side we attach our banner to */
position: absolute;
display: block;
width: 15.38em;
height: 1.54em;
top: 3.23em;
right: -3.23em;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.github-fork-ribbon:before {
content: "";
/* Add a bit of padding to give some substance outside the "stitching" */
padding: .38em 0;
/* Set the base colour */
background-color: #a00;
/* Set a gradient: transparent black at the top to almost-transparent black at the bottom */
background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15)));
background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
/* Add a drop shadow */
-webkit-box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);
box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);
pointer-events: auto;
}
.github-fork-ribbon:after {
/* Set the text from the data-ribbon attribute */
content: attr(data-ribbon);
/* Set the text properties */
color: #fff;
font: 700 1em "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.54em;
text-decoration: none;
text-shadow: 0 -.08em rgba(0, 0, 0, 0.5);
text-align: center;
text-indent: 0;
/* Set the layout properties */
padding: .15em 0;
margin: .15em 0;
/* Add "stitching" effect */
border-width: .08em 0;
border-style: dotted;
border-color: #fff;
border-color: rgba(255, 255, 255, 0.7);
}
.github-fork-ribbon.left-top, .github-fork-ribbon.left-bottom {
right: auto;
left: 0;
}
.github-fork-ribbon.left-bottom, .github-fork-ribbon.right-bottom {
top: auto;
bottom: 0;
}
.github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after {
right: auto;
left: -3.23em;
}
.github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after {
top: auto;
bottom: 3.23em;
}
.github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after {
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_0.md
================================================
---
version: '3.0.0'
---
- Java 8
- Macros have access to all variables within the template and are no longer restricted to a "local scope"
- New signature for filters/functions/tests that accepts the PebbleTemplate, EvaluationContext, and line number
- Converted EvaluationContext to an interface with limited access to the underlying implementation
- A "null" used as a boolean expression now throws an error if strict variables is true, treated as "false" otherwise
- Improved error reporting
- Java8 Date API support on date filter
- Replace guava cache with caffeine
- Add String Interpolation (#235)
- Add the possibility to extend attribute access (#258)
- Remove DynamicAttributeProvider
- Render content on demand. Expose getLiteralTemplate(String templateName) (#295)
- Introduce SpecializedWriter, (#358)
- Many performance enhancements
- Method resolution with overloaded method signatures (#367)
- Use AbstractTemplateViewResolverProperties for spring-boot (#369)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_1.md
================================================
---
version: '3.0.1'
---
- Macros are restricted to a local scope (#371)
- Macros have access to global attributes via "_context" variable (#242)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_10.md
================================================
---
version: '3.0.10'
---
- Add support for JDK 9++ (#442, #443)
- Add macro support for errors(BindingResult) in spring extension
- Add spring-petclinic example
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_2.md
================================================
---
version: '3.0.2'
---
- Add PebbleCache interface and use ConcurrentMap instead of Caffeine as default template/tag cache (#381)
- Use unbescape instead of Coverity Escapers (#380)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_3.md
================================================
---
version: '3.0.3'
---
- Support more expressions for if operator (#387)
- Consider adding greedyMatchMethod to PebbleProperties (#388)
- Use locale from context for lower/upper filter (#390)
- Restore cache:invalidateAll method (#393)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_4.md
================================================
---
version: '3.0.4'
---
- Make NOT operator more tolerant to argument type (#394)
- Make ternary if operator more tolerant to argument type (#399)
- Adjust AndExpression and OrExpression logic (#398)
- Add JSON escaping as part of the default escaping strategies (#395)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_5.md
================================================
---
version: '3.0.5'
---
- Cannot access List methods (#402)
- Implement to boolean smart-casting in OR and AND operators (#405)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_6.md
================================================
---
version: '3.0.6'
---
- Check that parseNewVariableName() parses a valid identifier (#409)
- Set up Pebble documentation site using Orchid (#411)
- Add twig compatibility matrix documentation (#247)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_7.md
================================================
---
version: '3.0.7'
---
- Add Automatic-Module-Name to support Java 9 modules (#416)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_8.md
================================================
---
version: '3.0.8'
---
- Add split filter (#421)
================================================
FILE: docs/src/orchid/resources/changelog/v3_0_9.md
================================================
---
version: '3.0.9'
---
- Add embed tag (#319, #318, #224, #378, #434)
- Offline documentation (#282, #432)
- Make allowGetClass check case insensitive (#435, #436)
================================================
FILE: docs/src/orchid/resources/changelog/v3_1_0.md
================================================
---
version: '3.1.0'
---
- **BREAKING CHANGES**: Consider avoiding feature version in spring boot starter name (#459)
- Upgrade spring boot integration to Spring Boot 2.1 (#460)
- Allow override core operators (#455, #456)
- Allows variables to be set in templates when provided context is an immutable map (#453)
- Fixed a few typos in the docs (#452)
- Remove unnecessary variable 'state' in LexerImpl by using Stack (#450)
- Updates Orchid to 0.17.1 and fixes its breaking changes (#448)
- Null value in a Map should not throw AttributeNotFoundException (#447, #446)
================================================
FILE: docs/src/orchid/resources/changelog/v3_1_1.md
================================================
---
version: '3.1.1'
---
- Use a list of unsafeMethods and rename allowGetClass to allowUnsafeMethods (#454)
- PebbleTemplateAvailabilityProvider doesn't find template files from the classpath (#464)
- Added inline verbatim description to Verbatim tag documentation (#422)
- Fix issue of embed tag changing cached templates (#475)
- Fixes bug which allowed sequential String literals to be parsed correctly (#482)
- Update to spring boot 2.2 (486)
================================================
FILE: docs/src/orchid/resources/changelog/v3_1_2.md
================================================
---
version: '3.1.2'
---
- Add support for jdk13 (#487)
- Add basic support for Spring 5 WebFlux reactive in Spring Boot 2 (#333)
================================================
FILE: docs/src/orchid/resources/changelog/v3_1_3.md
================================================
---
version: '3.1.3'
---
- Allow treating all number literals as BigDecimals to avoid NumberFormatExceptions (#503)
- Custom operator can't use external context (#497)
- Allow all variable names to be retrieve from ScopeChain/Scope (#291)
- Empty map in strict mode does not throw exception (#491)
================================================
FILE: docs/src/orchid/resources/changelog/v3_1_4.md
================================================
---
version: '3.1.4'
---
- Slice filter: Use collection size when toIndex is greater than collection size (#504)
- Adjust spring boot doc (#509)
- Build with jdk14 (#508)
- Set proxyBeanMethods to false and build with spring boot 2.3 (#507)
- Add access to Spring Beans/request/session and response when using Pebble with WebFlux (#512)
- Remove allowUnsafeMethods property and replace it with methodAccessValidator. Default one is BlacklistMethodAccessValidtor (#511)
================================================
FILE: docs/src/orchid/resources/changelog/v3_1_5.md
================================================
---
version: '3.1.5'
---
- Add timeZone parameter to date filter (#530)
- Add setting to limit the size of output when evaluating a template (#529)
- Fix variable name starting with operator (#541)
- Added Servlet5Loader to support Servlet 5.0 (#549)
- Throw an error when an import alias references an invalid macro (#559)
- Special for loop variables class, to support all attribute resolvers (#560)
- Support all Java identifiers (#544)
- Provide option to customize stock extensions (#552)
- Cannot use sort filter right after split filter (#568)
- Support array of List in sort filter (#569)
- Extension global variables in macros (#571)
- Add base64 and sha256 filters (#574)
================================================
FILE: docs/src/orchid/resources/changelog/v3_1_6.md
================================================
---
version: '3.1.6'
---
- Make CacheKey class extendible (#604)
- Remove support for spring 1.5.x (#628)
- Fix for older Android versions (#585 #581 #580)
================================================
FILE: docs/src/orchid/resources/changelog/v3_2_0.md
================================================
---
version: '3.2.0'
---
- Add support for spring framework 6 and spring-boot 3 (#630)
- Bump minimum supported java version to 17 in pebble-spring6 and pebble-spring-boot-starter in order to work with
spring (#630)
- Add a memory loader that supports inheritance and doesn't require a filesystem. This is useful for applications
that retrieve templates from a database for example (#617).
- **BREAKING CHANGE**: Change default suffix to `.peb` instead of `.pebble` in spring boot autoconfiguration (#553)
- **BREAKING CHANGE**: Rename method `getInstance` to `createInstance` in `BinaryOperator` interface (#521)
- **BREAKING CHANGE**: Rename package from `com.mitchellbosecke` to `io.pebbletemplates` (#635)
================================================
FILE: docs/src/orchid/resources/changelog/v3_2_1.md
================================================
---
version: '3.2.1'
---
- Fix the null pointer exception on the replace filter (#650)
- Add native hints for GraalVM (#648)
================================================
FILE: docs/src/orchid/resources/changelog/v3_2_2.md
================================================
---
version: '3.2.2'
---
- Throw a more detailed exception from DefaultAttributeResolver (#653)
- Make expression nodes fully visitable (#657)
- Added getTemplates function to MemoryLoader (#660)
- Generate OSGi metadata into pebble jar to make it OSGi compliant (#663)
- Add flag for parameter name retention in maven compiler plugin (#664)
================================================
FILE: docs/src/orchid/resources/changelog/v3_2_3.md
================================================
---
version: '3.2.3'
---
- Dynamically open different feature according to different scenarios (#671)
- Fix the ability to provide your own PebbleViewResolver (#686)
================================================
FILE: docs/src/orchid/resources/changelog/v3_2_4.md
================================================
---
version: '3.2.4'
---
- Fix the problem of unit test failure under JDK 21 (#692)
- Fix the problem of not resolving reactive values in models (#691)
================================================
FILE: docs/src/orchid/resources/changelog/v4_0_0.md
================================================
---
version: '4.0.0'
---
# BREAKING CHANGES
- Add support for spring boot 4 (#704)
- Use one of the following artifactId according to the spring boot version that you are using
| ArtifactId | spring-boot version |
|-----------------------------------|---------------------|
| pebble-legacy-spring-boot-starter | 3.x.x |
| pebble-spring-boot-starter | 4.x.x |
- The following spring boot properties has been moved to `.servlet` or `.reactive`
| Old property | New Property |
|---------------------------------|-----------------------------------------|
| pebble.allowRequestOverride | pebble.servlet.allowRequestOverride |
| pebble.allowSessionOverride | pebble.servlet.allowSessionOverride |
| pebble.cache | pebble.servlet.cache |
| pebble.contentType | pebble.servlet.contentType |
| pebble.exposeRequestAttributes | pebble.servlet.exposeRequestAttributes |
| pebble.exposeSessionAttributes | pebble.servlet.exposeSessionAttributes |
| pebble.exposeSpringMacroHelpers | pebble.servlet.exposeSpringMacroHelpers |
| | pebble.reactive.mediaTypes |
For more information, please consult the spring-boot integration documentation in
the [Boot externalized configuration section](https://pebbletemplates.io/wiki/guide/spring-boot-integration/)
# New Features
- Add nl2br filter (#699)
================================================
FILE: docs/src/orchid/resources/changelog/v4_1_0.md
================================================
---
version: '4.1.0'
---
# BREAKING CHANGES
- If you do not provide a custom Loader, Pebble will now use only a `ClasspathLoader` by default, same as the spring autoconfiguration.
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.
- Modify the `FileLoader` to use a mandatory base directory parameter.
# Security
- Fix [CVE-2025-1686](https://nvd.nist.gov/vuln/detail/CVE-2025-1686) (#715)
# New Features
- Use a default existing format of `yyyy-MM-dd'T'HH:mm:ssZ` when using date filter with a string (#677)
- Look for exact method / field match when doing reflection. Look for method get/is/has if none match (#712)
- Support dynamic named arguments (#741)
# Bug Fixes
- NaN must return false instead of throwing an exception (#695)
# Dependency Upgrades
- Upgrade to SLF4J 2.0.17 (#709)
================================================
FILE: docs/src/orchid/resources/changelog/v4_1_1.md
================================================
---
version: '4.1.1'
---
# New Features
- Expose AST Root Node to Enable Custom NodeVisitor Implementation (#701)
# Bug Fixes
- Catch all exceptions in DelegatingLoader (#765)
================================================
FILE: docs/src/orchid/resources/config.yml
================================================
site:
about:
siteName: 'Pebble Templates'
subtitle: 'A lightweight but rock solid Java templating engine.'
theme:
primaryColor: '#72A6D0'
menu:
- type: 'separator'
title: 'About'
- type: 'page'
title: 'Home'
itemId: 'Home'
- type: 'page'
itemId: 'Spring Boot Integration'
- type: 'link'
title: 'Spring petclinic example'
url: 'https://github.com/PebbleTemplates/spring-petclinic'
- type: 'page'
itemId: 'Twig Compatibility'
- type: 'page'
itemId: 'Contributing'
- type: 'page'
itemId: 'Changelog'
- type: 'link'
title: 'Offline Documentation (PDF)'
url: '#{$0|baseUrl}/wiki/book.pdf'
- type: 'link'
title: 'Buy me a coffee'
url: 'https://buymeacoffee.com/erbussierel'
- type: 'separator'
title: 'Wiki'
- type: 'wiki'
- type: 'separator'
title: 'API Docs'
- type: 'page'
title: 'Javadocs'
itemId: 'io.pebbletemplates'
extraCss:
- 'assets/css/pygments.scss'
- 'assets/css/orchidJavadoc.scss'
- 'assets/css/github-fork-ribbon.css'
services:
publications:
stages:
- type: githubPages
branch: gh-pages
publishType: CleanBranch
username: PebbleTemplates
repo: pebble
- type: githubReleases
repo: 'PebbleTemplates/pebble'
allPages:
components:
- type: 'pageContent'
- type: 'prism'
languages:
- 'java'
- 'twig'
- 'markup'
wiki:
defaultConfig:
includeIndexInPageTitle: false
createPdf: true
javadoc:
sourceDirs:
- '../../../../pebble/src/main/java'
pages:
menu:
- type: 'javadocPackages'
- type: 'javadocClasses'
================================================
FILE: docs/src/orchid/resources/data/contributors.yml
================================================
- name: Mitchell Bösecke
link: https://github.com/mbosecke
- name: Eric Bussieres
link: https://github.com/ebussieres
- name: Héctor López
link: https://github.com/hectorlf
- name: Djalma Olivera
link: https://github.com/djalmaoliveira
- name: Vladimir Loshchin
link: https://github.com/badbob
- name: yanxiyue
link: https://github.com/yanxiyue
- name: Vladimir Danilov
link: https://github.com/diasonti
- name: Casey Brooks
link: https://github.com/cjbrooks12
- name: Bastien Jansen
link: https://github.com/bjansen
================================================
FILE: docs/src/orchid/resources/data/twig-compatibility/filters.yml
================================================
- name: 'abs'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/abs.html'
support: 'full'
page: 'abs'
- name: 'batch'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/batch.html'
support: 'none'
- name: 'capitalize'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/capitalize.html'
support: 'full'
page: 'capitalize'
- name: 'convert_encoding'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/convert_encoding.html'
support: 'none'
- name: 'date'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/date.html'
support: 'partial'
page: 'date'
notes: 'This filter uses `java.util.Date` date formatting instead of PHP-style formatting.'
- name: 'date_modify'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/date_modify.html'
support: 'none'
- name: 'default'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/default.html'
support: 'full'
page: 'default'
- name: 'escape'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/escape.html'
support: 'full'
page: 'escape'
- name: 'first'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/first.html'
support: 'full'
page: 'first'
- name: 'format'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/format.html'
support: 'full'
page: 'format'
- name: 'join'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/join.html'
support: 'none'
page: 'join'
- name: 'json_encode'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/json_encode.html'
support: 'none'
- name: 'keys'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/keys.html'
support: 'none'
- name: 'last'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/last.html'
support: 'full'
page: 'last'
- name: 'length'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/length.html'
support: 'full'
page: 'length'
- name: 'lower'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/lower.html'
support: 'full'
page: 'lower'
- name: 'merge'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/merge.html'
support: 'none'
- name: 'nl2br'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/nl2br.html'
support: 'full'
page: 'nl2br'
- name: 'number_format'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/number_format.html'
support: 'partial'
page: 'numberformat'
notes: 'Filter is named `numberformat`, and uses `java.text.DecimalFormat` formatting instead of PHP-style formatting.'
- name: 'raw'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/raw.html'
support: 'full'
page: 'raw'
- name: 'replace'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/replace.html'
support: 'full'
page: 'replace'
- name: 'reverse'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/reverse.html'
support: 'full'
page: 'reverse'
- name: 'round'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/round.html'
support: 'none'
- name: 'slice'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/slice.html'
support: 'full'
page: 'slice'
- name: 'sort'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/sort.html'
support: 'full'
page: 'sort'
- name: 'split'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/split.html'
support: 'full'
page: 'split'
- name: 'striptags'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/striptags.html'
support: 'none'
- name: 'title'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/title.html'
support: 'full'
page: 'title'
- name: 'trim'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/trim.html'
support: 'full'
page: 'trim'
- name: 'upper'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/upper.html'
support: 'full'
page: 'upper'
- name: 'url_encode'
twigLink: 'https://twig.symfony.com/doc/2.x/filters/url_encode.html'
support: 'partial'
page: 'urlencode'
notes: 'Filter is named `urlencode`.'
================================================
FILE: docs/src/orchid/resources/data/twig-compatibility/functions.yml
================================================
- name: 'attribute'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/attribute.html'
support: 'none'
- name: 'block'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/block.html'
support: 'full'
page: 'block'
- name: 'constant'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/constant.html'
support: 'none'
- name: 'cycle'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/cycle.html'
support: 'none'
- name: 'date'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/date.html'
support: 'none'
- name: 'dump'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/dump.html'
support: 'none'
- name: 'include'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/include.html'
support: 'none'
- name: 'max'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/max.html'
support: 'full'
page: 'max'
- name: 'min'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/min.html'
support: 'full'
page: 'min'
- name: 'parent'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/parent.html'
support: 'full'
page: 'parent'
- name: 'random'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/random.html'
support: 'none'
- name: 'range'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/range.html'
support: 'full'
page: 'range'
- name: 'source'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/source.html'
support: 'none'
- name: 'template_from_string'
twigLink: 'https://twig.symfony.com/doc/2.x/functions/template_from_string.html'
support: 'none'
================================================
FILE: docs/src/orchid/resources/data/twig-compatibility/operators.yml
================================================
- name: 'in'
twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#containment-operator'
support: 'none'
page: 'contains'
notes: '`contains` can be used instead'
- name: 'is'
twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#test-operator'
support: 'full'
page: 'is'
- name: 'Math (+, -, /, %, //, *, **)'
twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#math'
support: 'partial'
page: 'math'
notes: '`//` and `**` not supported'
- name: 'Logic (and, or, not, (), b-and, b-xor, b-or)'
twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#logic'
support: 'partial'
page: 'logic'
notes: 'Bitwise logical operators are not supported'
- name: 'Comparisons (==, !=, <, >, >=, <=, ===, starts with, ends with, matches)'
twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#comparisons'
support: 'partial'
page: 'comparisons'
notes: '`===`, `starts with`, `ends with`, and `matches` not supported'
- name: 'Others (.., |, ~, ., [], ?:)'
twigLink: 'https://twig.symfony.com/doc/2.x/templates.html#other-operators'
support: 'full'
page: 'others'
notes: 'subscript operator `[]` index starts at 0, not 1'
================================================
FILE: docs/src/orchid/resources/data/twig-compatibility/tags.yml
================================================
- name: 'autoescape'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/autoescape.html'
support: 'full'
page: 'autoescape'
- name: 'block'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/block.html'
support: 'full'
page: 'block'
- name: 'do'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/do.html'
support: 'none'
- name: 'embed'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/embed.html'
support: 'partial'
page: 'embed'
notes: '`only` clause and `ignore missing` clause not supported'
- name: 'extends'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/extends.html'
support: 'full'
page: 'extends'
- name: 'filter'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/filter.html'
support: 'full'
page: 'filter'
- name: 'flush'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/flush.html'
support: 'full'
page: 'flush'
- name: 'for'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/for.html'
support: 'full'
page: 'for'
- name: 'from'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/from.html'
support: 'full'
page: 'from'
- name: 'if'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/if.html'
support: 'full'
page: 'if'
- name: 'import'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/import.html'
support: 'full'
page: 'import'
- name: 'include'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/include.html'
support: 'partial'
page: 'include'
notes: '`only` clause and `ignore missing` clause not supported'
- name: 'macro'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/macro.html'
support: 'full'
page: 'macro'
- name: 'sandbox'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/sandbox.html'
support: 'none'
- name: 'set'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/set.html'
support: 'full'
page: 'set'
- name: 'spaceless'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/spaceless.html'
support: 'none'
- name: 'use'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/use.html'
support: 'none'
- name: 'verbatim'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/verbatim.html'
support: 'full'
page: 'verbatim'
- name: 'with'
twigLink: 'https://twig.symfony.com/doc/2.x/tags/with.html'
support: 'none'
================================================
FILE: docs/src/orchid/resources/data/twig-compatibility/tests.yml
================================================
- name: 'constant'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/constant.html'
support: 'none'
- name: 'defined'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/defined.html'
support: 'none'
- name: 'divisible by'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/divisibleby.html'
support: 'none'
- name: 'empty'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/empty.html'
support: 'full'
page: 'empty'
- name: 'even'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/even.html'
support: 'full'
page: 'even'
- name: 'iterable'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/iterable.html'
support: 'full'
page: 'iterable'
- name: 'null'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/null.html'
support: 'full'
page: 'null'
- name: 'odd'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/odd.html'
support: 'full'
page: 'odd'
- name: 'same as'
twigLink: 'https://twig.symfony.com/doc/2.x/tests/sameas.html'
support: 'none'
================================================
FILE: docs/src/orchid/resources/homepage.md
================================================
---
---
Pebble is a Java templating engine inspired by Twig 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.
## Features
* **Rich set of built-in tags and filters**
* **Template inheritance**: extract common areas of your content in a single ‘layout’ and make
your templates inherit this layout.
* **Extensible language**: new tags, filters and functions can be added to Pebble very easily.
If you already know Twig, you can compare both engines in {{ anchor('the compatibility matrix', 'Twig Compatibility') }}.
## Basic Usage
First, add the following dependency to your pom.xml:
```xml
io.pebbletemplatespebble{{ site.version }}
```
Then create a template in your WEB-INF folder. Let's start with a base template that all
other templates will inherit from, name it "base.html":
```twig
{% verbatim %}
{% block title %}My Website{% endblock %}
{% block content %}{% endblock %}
{% endverbatim %}
```
Then create a template that extends base.html, call it "home.html":
```twig
{% verbatim %}
{% extends "base.html" %}
{% block title %} Home {% endblock %}
{% block content %}
Home
Welcome to my home page. My name is {{ name }}.
{% endblock %}
{% endverbatim %}
```
Now we want to compile the template, and render it:
```java
PebbleEngine engine = new PebbleEngine.Builder().build();
PebbleTemplate compiledTemplate = engine.getTemplate("home.html");
Map context = new HashMap<>();
context.put("name", "Mitchell");
Writer writer = new StringWriter();
compiledTemplate.evaluate(writer, context);
String output = writer.toString();
```
The output should result in the following:
```twig
Home
Home
Welcome to my home page. My name is Mitchell.
```
For more information on installation and configuration, see {{ anchor('the installation guide', 'Installation and Configuration') }}.
For more information on basic usage, see {{ anchor('the basic usage guide', 'Basic Usage') }}.
For Spring Boot integration, see {{ anchor('the Spring Boot integration guide', 'Spring Boot Integration') }}.
================================================
FILE: docs/src/orchid/resources/pages/CNAME
================================================
---
renderMode: raw
usePrettyUrl: false
---
pebbletemplates.io
================================================
FILE: docs/src/orchid/resources/pages/changelog.md
================================================
---
components:
- type: changelog
---
================================================
FILE: docs/src/orchid/resources/pages/contributing.md
================================================
---
---
# Contributing
## Help Wanted
Contributors of all types are welcome but most importantly I am looking for help with the following:
- IDE Integration
- API Feedback
- Testing and Bug Reports
- Performance Optimizations
- Improving Thread Safety
General 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).
## Getting Started
- Fork the [repository](https://github.com/PebbleTemplates/pebble)
- Make the fix.
- Use maven to build and test:
- `mvn clean install` from root to build Pebble
- `mvn orchid:serve -P doc` from `docs/` to build and serve documentation on http://localhost:8080
- Submit a pull request
## Understanding the Code
There are a few major components that play a vital role in the compilation process. The main
`PebbleEngine`, the `LexerImpl`, and the `ParserImpl`.
The `PebbleEngine` is responsible for beginning the template compilation process. It begins by passing
the template source to the `Lexer`.
The `Lexer` is responsible for converting the template into a bunch of `Token` objects. A token is the smallest
distinguishable unit in a template, i.e. it can not be broken down into more specific objects. A token can
represent a delimiter (eg. `{{ '{{' }}` ), or a function name, a string, plain text, or many other things. Once the
lexer has established the entire stream of tokens which collectively make up the entire template, it returns
this `TokenStream` back to the main `PebbleEngine`.
The `PebbleEngine` now sends the `TokenStream` to the `Parser` which is responsible for turning those token objects
into `Node` objects. The nodes are built in a hierarchical fashion and together they make up the abstract
syntax tree. The parser uses the help of the main `ExpressionParser` as well as the `TokenParser` objects which
are provided by the extensions. Each node is responsible for rendering itself during the template
evaluation phase.
The `PebbleEngine` now has a tree of node objects, beginning with the `NodeRoot`. It will then create a
`PebbleTemplateImpl` object with this root node as an argument. The last step before returning this template
to the user is to invoke both the internal and the user-provided `NodeVisitor` objects on the node tree.
Node visitors are used to process the node tree, perhaps to add extra functionality (ex. the built-in
autoescaper uses a node visitor to wrap the expressions of nodes with an escape function). Once this
processing is completed, the template is placed into the cache and finally returned to the user.
The user will then call `evaluate()` on this template which begins the evaluation process. The template
will call `render()` on it's root node which in turn will call `render()` on each of it's children nodes.
This process recurses throughout the whole node tree until all nodes have rendered themselves to the
provided `Writer` object.
## Documentation
The documentation website is generated using [Orchid](https://orchid.netlify.com/).
The documentation files are under the `src/orchid/resources` directory.
## Contributing Code
Currently there aren't any formal guidelines. Just ensure that your changes include any
necessary unit tests and send me a pull request on [github](https://github.com/PebbleTemplates/pebble)!
## Standards and Guidelines
### Code Formatting Standards
1. Use [Google Style Guide](https://google.github.io/styleguide/javaguide.html) formatting conventions.
1. Configuring IntelliJ to use the Google Style Guide Formatter.
1. Clone the [Google Style Git Repo](https://github.com/google/styleguide).
2. Open **Preferences -> Editor -> Code Style -> Java**.
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).
2. Configuring Eclipse to use the Google Style Guide Formatter.
1. Clone the [Google Style Git Repo](https://github.com/google/styleguide).
2. Select **Preference -> Java -> Code Style -> Formatter**.
3. Select **Import** and specify the file **eclipse-java-google-style.xml** from the [Google Style Git Repo](https://github.com/google/styleguide).
### Unit Test Guidelines
1. Use [AssertJ]() for all Unit and Integration tests.
2. A javadoc description should be included for each test method.
3. The purpose of each test should be clear from the test method name.
## Acknowledgements
Thanks to all the following contributors who are helping to make Pebble the best template engine available for Java.
{%- for contributor in data.contributors %}
* [{{ contributor.name }}]({{ contributor.link }})
{%- endfor %}
================================================
FILE: docs/src/orchid/resources/pages/twig-compatibility.peb
================================================
---
title: Twig Compatibility
---
While Pebble was inspired by and generally has high compatibility with Twig, it is neither a complete nor an exact Twig
implementation. There are features of Twig not supported by Pebble, there are features implemented in Pebble that are
not supported by Twig, and some features might behave differently or use a different name or syntax. This page outlines
the similarities and differences between Pebble and Twig.
{{ compat('Tags', data['twig-compatibility'].tags ) }}
{{ compat('Filters', data['twig-compatibility'].filters ) }}
{{ compat('Functions', data['twig-compatibility'].functions ) }}
{{ compat('Tests', data['twig-compatibility'].tests ) }}
{{ compat('Operators', data['twig-compatibility'].operators ) }}
{% macro compat(type, items) %}
{% scripts %}
{% include '?trackingBodyEnd' %}
================================================
FILE: docs/src/orchid/resources/wiki/filter/abbreviate.md
================================================
# `abbreviate`
The `abbreviate` filter will abbreviate a string using an ellipsis. It takes one argument which is the max
width of the desired output including the length of the ellipsis.
```twig
{{ "this is a long sentence." | abbreviate(7) }}
```
The above example will output the following:
```twig
this...
```
## Arguments
- length
================================================
FILE: docs/src/orchid/resources/wiki/filter/abs.md
================================================
# `abs`
The `abs` filter is used to obtain the absolute value.
```twig
{{ -7 | abs }}
{# output: 7 #}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/base64decode.md
================================================
---
---
# `base64decode`
The `base64decode` filter takes the given input, Base64-decodes it, if possible, and returns the byte array converted to UTF-8 String.
Applying the filter on an incorrect base64-encoded string will throw an exception.
```twig
{% verbatim %}{{ "dGVzdA==" | base64decode }}{% endverbatim %}
```
The above example will output the following:
```
test
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/base64encode.md
================================================
---
---
# `base64encode`
The `base64encode` filter takes the given input, converts it to an UTF-8 String (`.toString()`) and Base64-encodes it.
```twig
{% verbatim %}{{ "test" | base64encode }}{% endverbatim %}
```
The above example will output the following:
```
dGVzdA==
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/capitalize.md
================================================
---
---
# `capitalize`
The `capitalize` filter will capitalize the first letter of the string.
```twig
{% verbatim %}
{{ "article title" | capitalize }}
{% endverbatim %}
```
The above example will output the following:
```twig
Article title
```
See also: {{ anchor('title') }}
================================================
FILE: docs/src/orchid/resources/wiki/filter/date.md
================================================
# `date`
The `date` filter formats a date in a variety of formats. It can handle old-school `java.util.Date`,
Java 8 `java.time` constructs like `OffsetDateTime` and timestamps in milliseconds from the epoch.
The filter will construct a `java.text.SimpleDateFormat` or `java.time.format.DateTimeFormatter` using the provided
pattern and then use this newly created format to format the provided date object. If you don't provide a pattern,
either `DateTimeFormatter.ISO_DATE_TIME` or `yyyy-MM-dd'T'HH:mm:ssZ` will be used.
```twig
{{ user.birthday | date("yyyy-MM-dd") }}
```
An alternative way to use this filter is to use it on a string but then provide two arguments:
the first is the desired pattern for the output, and the second is the existing format used to parse the
input string into a `java.util.Date` object.
```twig
{{ "July 24, 2001" | date("yyyy-MM-dd", existingFormat="MMMM dd, yyyy") }}
```
The above example will output the following:
```twig
2001-07-24
```
## Time zones
If the provided date has time zone info (e.g. `OffsetDateTime`) then it will be used. If the provided date has no
time zone info, by default the system time zone will be used. If you need to use a specific
time zone then you can pass in a `timeZone` parameter any string that's understood by `ZoneId` / `ZoneInfo`:
```twig
{# the timeZone parameter will be ignored #}
{{ someOffsetDateTime | date("yyyy-MM-dd'T'HH:mm:ssX", timeZone="UTC") }}
{# the provided time zone will override the system default #}
{{ someInstant | date("yyyy-MM-dd'T'HH:mm:ssX", timeZone="Pacific/Funafuti") }}
```
## Arguments
- format
- existingFormat
- timeZone
================================================
FILE: docs/src/orchid/resources/wiki/filter/default.md
================================================
## `default`
The `default` filter will render a default value if and only if the object being filtered is empty.
A variable is empty if it is null, an empty string, an empty collection, or an empty map.
```twig
{{ user.phoneNumber | default("No phone number") }}
```
In 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:
```twig
{{ foo.bar.baz | default("No baz") }}
```
Note that the default filter will suppress any `AttributeNotFoundException` exceptions that will usually be thrown when `strictVariables` is set to `true`.
## Arguments
- default
================================================
FILE: docs/src/orchid/resources/wiki/filter/escape.md
================================================
---
---
# `escape`
The `escape` filter will turn special characters into safe character references in order to avoid XSS
vulnerabilities. This filter will typically only need to be used if you've turned off autoescaping.
```twig
{% verbatim %}
{{ "
" | escape }}
{# output: <div> #}
{% endverbatim %}
```
Please read the {{ anchor('escaping guide', 'Escaping') }} for more information about escaping.
## Arguments
- strategy
================================================
FILE: docs/src/orchid/resources/wiki/filter/first.md
================================================
# `first`
The `first` filter will return the first item of a collection, or the first letter of a string.
```twig
{{ users | first }}
{# will output the first item in the collection named 'users' #}
{{ 'Mitch' | first }}
{# will output 'M' #}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/format.md
================================================
# `format`
The `format` filter formats a string by replacing placeholders with the provided arguments (placeholders follows the `String.format` notation).
```twig
{{ 'Hello %s!' | format('World') }}
{# Hello World! #}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/join.md
================================================
# `join`
The `join` filter will concatenate all items of a collection into a string. An optional argument can be given
to be used as the separator between items.
```twig
{#
List names = new ArrayList<>();
names.add("Alex");
names.add("Joe");
names.add("Bob");
#}
{{ names | join(',') }}
{# will output: Alex,Joe,Bob #}
```
## Arguments
- separator
================================================
FILE: docs/src/orchid/resources/wiki/filter/last.md
================================================
# `last`
The `last` filter will return the last item of a collection, or the last letter of a string.
```twig
{{ users | last }}
{# will output the last item in the collection named 'users' #}
{{ 'Mitch' | last }}
{# will output 'h' #}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/length.md
================================================
# `length`
The `length` filter returns the number of items of collection, map or the length of a string:
```twig
{% if users|length > 10 %}
...
{% endif %}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/lower.md
================================================
# `lower`
The `lower` filter makes an entire string lower case.
```twig
{{ "THIS IS A LOUD SENTENCE" | lower }}
```
The above example will output the following:
```twig
this is a loud sentence
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/nl2br.md
================================================
# `nl2br`
The `nl2br` filter converts newline characters (`\r`, `\n`, `\r\n`) in a string to HTML line break tags (` `). This
is useful when you want to preserve line breaks in text when displaying it in a web page.
```pebble
{{ "I like Pebble.\nYou will like it too."|nl2br }}
{# outputs
I like Pebble. You will like it too.
#}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/numberformat.md
================================================
# `numberformat`
The `numberformat` filter is used to format a decimal number. Behind the scenes it uses `java.text.DecimalFormat`.
```twig
{{ 3.141592653 | numberformat("#.##") }}
```
The above example will output the following:
```twig
3.14
```
## Arguments
- format
================================================
FILE: docs/src/orchid/resources/wiki/filter/raw.md
================================================
---
---
# `raw`
The `raw` filter prevents the output of an expression from being escaped by the autoescaper.
The `raw` filter must be the very last operation performed within the expression otherwise the
autoescaper will deem the expression as potentially unsafe and escape it regardless.
```twig
{% verbatim %}
{% set danger = "
" %}
{{ danger | upper | raw }}
{# ouptut:
#}
{% endverbatim %}
```
If the `raw` filter is not the last operation performed then the expression will be escaped:
```twig
{% verbatim %}
{% set danger = "
" %}
{{ danger | raw | upper }}
{# output: <DIV> #}
{% endverbatim %}
```
Please read the {{ anchor('escaping guide', 'Escaping') }} for more information about escaping.
================================================
FILE: docs/src/orchid/resources/wiki/filter/replace.md
================================================
# `replace`
The 'replace' filter formats a given string by replacing the placeholders (placeholders are free-form):
```twig
{{ "I like %this% and %that%." | replace({'%this%': foo, '%that%': "bar"}) }}
```
## Arguments
- placeholders to replace
================================================
FILE: docs/src/orchid/resources/wiki/filter/reverse.md
================================================
# `reverse`
The 'reverse' filter reverses a List:
```twig
{% for user in users | reverse %} {{ user }} {% endfor %}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/rsort.md
================================================
# `rsort`
The `rsort` filter will sort a list in reversed order. The items of the list must implement `Comparable`.
```twig
{% for user in users | rsort %}
{{ user.name }}
{% endfor %}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/sha256.md
================================================
---
---
# `sha256`
The `sha256` filter returns the SHA-256 hash of the given UTF-8 String.
```twig
{% verbatim %}{{ "test" | sha256 }}{% endverbatim %}
```
The above example will output the following:
```
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/slice.md
================================================
# `slice`
The `slice` filter returns a portion of a list, array, or string.
```twig
{{ ['apple', 'peach', 'pear', 'banana'] | slice(1,3) }}
{# results in: [peach, pear] #}
{{ 'Mitchell' | slice(1,3) }}
{# results in: 'it' #}
```
## Arguments
- `fromIndex`: 0-based and inclusive
- `toIndex`: 0-based and exclusive
================================================
FILE: docs/src/orchid/resources/wiki/filter/sort.md
================================================
# `sort`
The `sort` filter will sort a list. The items of the list must implement `Comparable`.
```twig
{% for user in users | sort %}
{{ user.name }}
{% endfor %}
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/split.md
================================================
# `split`
The `split` filter splits a string by the given delimiter and returns a list of strings.
```twig
{% set foo = "one,two,three" | split(',') %}
{# foo contains ['one', 'two', 'three'] #}
```
You can also pass a limit argument:
- 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;
- If `limit` is negative, then the pattern will be applied as many times as possible and the array can have any length;
- 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;
```twig
{% set foo = "one,two,three,four,five" | split(',', 3) %}
{# foo contains ['one', 'two', 'three,four,five'] #}
```
## Arguments
- delimiter: The delimiter
- limit: The limit argument
================================================
FILE: docs/src/orchid/resources/wiki/filter/title.md
================================================
---
---
# `title`
The `title` filter will capitalize the first letter of each word.
```twig
{% verbatim %}
{{ "article title" | title }}
{% endverbatim %}
```
The above example will output the following:
```twig
Article Title
```
See also: {{ anchor('capitalize') }}
================================================
FILE: docs/src/orchid/resources/wiki/filter/trim.md
================================================
# `trim`
The `trim` filter is used to trim whitespace off the beginning and end of a string.
```twig
{{ " This text has too much whitespace. " | trim }}
```
The above example will output the following:
```twig
This text has too much whitespace.
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/upper.md
================================================
# `upper`
The `upper` filter makes an entire string upper case.
```twig
{{ "this is a quiet sentence." | upper }}
```
The above example will output the following:
```twig
THIS IS A QUIET SENTENCE.
```
================================================
FILE: docs/src/orchid/resources/wiki/filter/urlencode.md
================================================
# `urlencode`
The `urlencod` translates a string into `application/x-www-form-urlencoded` format using the "UTF-8" encoding scheme.
```twig
{{ "The string ü@foo-bar" | urlencode }}
```
The above example will output the following:
```twig
The+string+%C3%BC%40foo-bar
```
================================================
FILE: docs/src/orchid/resources/wiki/function/blockFunction.md
================================================
---
---
# `block`
The `block` function is used to render the contents of a block more than once. It is not to be confused
with the block *tag* which is used to declare blocks.
The following example will render the contents of the "post" block twice; once where it was declared
and again using the `block` function:
```twig
{% verbatim %}
{% block "post" %} content {% endblock %}
{{ block("post") }}
{% endverbatim %}
```
The above example will output the following:
```twig
content
content
```
## Performance Warning
The `block` function will impair the use of the {{ anchor('flush') }} tag used within the block being rendered.
It is typically okay for a block to use the `flush` tag which will flush the already-rendered
content to the user-provided `Writer` but the block function will internally use it's own `StringWriter` and
therefore flushing inside the block will no longer do any good (nor will it do harm).
================================================
FILE: docs/src/orchid/resources/wiki/function/i18n.md
================================================
# `i18n`
The `i18n` function is used to retrieve messages from a locale-specific `ResourceBundle`.
Every `PebbleTemplate` is assigned a default locale from the `PebbleEngine`. At the point of evaluation, this
locale can be changed with an argument to the `evaluate(...)` method of the individual template.
The `i18n` function wraps around `ResourceBundle.getBundle(name, locale).getObject(key)`.
The first argument to the `i18n` function is the name of the bundle and the second argument is the key
within the bundle.
```twig
{{ i18n("messages","greeting") }}
```
The above example assumes you have `messages.properties` on your classpath and that that file contains
a key by the name of `greeting`. If the locale of that template was `es_US` for example, it would look for a
`message_es_US.properties` file instead.
Going a little further, you can use variables within your message and pass a list of params to this
function which will replace your variables using `MessageFormat`:
```twig
{# greeting.someone=Hello, {0} #}
{{ i18n("messages","greeting", "Jacob") }}
{# output: Hello, Jacob #}
```
## Arguments
- bundle
- key
- params
================================================
FILE: docs/src/orchid/resources/wiki/function/max.md
================================================
# `max`
The `max` function will return the largest of it's numerical arguments.
```twig
{{ max(user.age, 80) }}
```
================================================
FILE: docs/src/orchid/resources/wiki/function/min.md
================================================
# `min`
The `min` function will return the smallest of it's numerical arguments.
```twig
{{ min(user.age, 80) }}
```
================================================
FILE: docs/src/orchid/resources/wiki/function/parent.md
================================================
# `parent`
The `parent` function is used inside of a block to render the content that the parent template would
have rendered inside of the block had the current template not overriden it. It is similar to Java's `super` keyword.
Let's assume you have a template, "parent.peb" that looks something like this:
```twig
{% block "content" %}
parent contents
{% endblock %}
```
And then you have another template, "child.peb" that extends "parent.peb":
```twig
{% extends "parent.peb" %}
{% block "content" %}
child contents
{{ parent() }}
{% endblock %}
```
The output will look something like the following:
```twig
parent contents
child contents
```
================================================
FILE: docs/src/orchid/resources/wiki/function/range.md
================================================
# `range`
The `range` function will return a list containing an arithmetic progression of numbers:
```twig
{% for i in range(0, 3) %}
{{ i }},
{% endfor %}
{# outputs 0, 1, 2, 3, #}
```
When step is given (as the third parameter), it specifies the increment (or decrement):
```twig
{% for i in range(0, 6, 2) %}
{{ i }},
{% endfor %}
{# outputs 0, 2, 4, 6, #}
```
Pebble built-in .. operator is just a shortcut for the range function with a step of 1+
```twig
{% for i in 0..3 %}
{{ i }},
{% endfor %}
{# outputs 0, 1, 2, 3, #}
```
================================================
FILE: docs/src/orchid/resources/wiki/guide/basic-usage.md
================================================
---
---
# Basic Usage
## Introduction
Pebble templates can be used to generate any sort of textual output. It is typically used to generate HTML but it can
also be used to create CSS, XML, JS, etc. A template itself will contain whatever language you are attempting to output
alongside Pebble-specific features and syntax. Here is a simple example that will generate a trivial HTML page:
```twig
{% verbatim %}
{{ websiteTitle }}
{{ content }}
{%- endverbatim %}
```
When you evaluate the template you will provide it with a "context" which is just a map of variables.
This context should include the two variables above, `websiteTitle` and `content`.
## Set Up
You will want to begin by creating a PebbleEngine object which is responsible for compiling your templates:
```java
PebbleEngine engine = new PebbleEngine.Builder().build();
```
And now, with your new PebbleEngine instance you can start compiling templates:
```java
PebbleTemplate compiledTemplate = engine.getTemplate("templates/home.html");
```
Finally, simply provide your compiled template with a java.io.Writer object and a Map of variables (the context)
to get your output!
```java
Writer writer = new StringWriter();
Map context = new HashMap<>();
context.put("websiteTitle", "My First Website");
context.put("content", "My Interesting Content");
compiledTemplate.evaluate(writer, context);
String output = writer.toString();
```
## Syntax Reference
There are two primary delimiters used within a Pebble template: `{% verbatim %}{{ ... }}{% endverbatim %}` and `{% verbatim %}{% ... %}{% endverbatim %}`. The first set of delimiters
will output the result of an expression. Expressions can be very simple (ex. a variable name) or much more complex.
The second set of delimiters is used to change the control flow of the template; it can contain an if-statement,
define a parent template, define a new block, etc.
## Variables
You can print variables directly to the output; for example, if the context contains a variable called `foo` which is a
String with the value "bar" you can do the following which will output "bar".
```twig
{% verbatim %}{{ foo }}{% endverbatim %}
```
You can use the dot (.) notation to access attributes of variables. If the attribute contains any atypical
characters, you can use the subscript notation ([]) instead.
```twig
{% verbatim %}
{{ foo.bar }}
{{ foo["bar"] }}
{%- endverbatim %}
```
Behind the scenes `foo.bar` will attempt the following techniques to to access the `bar` attribute of the `foo`
variable:
- If `foo` is a Map, `foo.get("bar")`
- `foo.getBar()`
- `foo.isBar()`
- `foo.hasBar()`
- `foo.bar()`
- `foo.bar`
Additionally, if `foo` is a List, then `foo[0]` can be used instead of `foo.get(0)`.
If the value of variable (or attribute) is null it will output an empty string.
## Type Safety
Pebble templates are dynamically typed and any possible type safety issues won't occur until the actual runtime
evaluation of your templates. Pebble does however allow you to choose how to handle type safety issues with the use
of it's `strictVariables` setting. By default, `strictVariables` is set to `false` which means that the following:
```twig
{% verbatim %}{{ foo.bar }}{% endverbatim %}
```
will print an empty string even if the object `foo` does not actually have an attribute called `bar`.
If `strictVariables` is set to true, the above expression would throw an exception.
When `strictVariables` is set to false your expressions are also null safe. The following expression will print an
empty string even if foo and/or bar are null:
```twig
{% verbatim %}{{ foo.bar.baz }}{% endverbatim %}
```
The {{ anchor('default') }} filter might come in handy for the above situations.
## Filters
Output can be further modified with the use of filters. Filters are separated from the variable using a
pipe symbol (`|`) and may have optional arguments in parentheses. Multiple filters can be chained and the output
of one filter is applied to the next.
```twig
{% verbatim %}{{ "If life gives you lemons, eat lemons." | upper | abbreviate(13) }}{% endverbatim %}
```
The above example will output the following:
```twig
IF LIFE GI...
```
## Functions
Whereas filters are intended to modify existing content/variables, functions are intended to generate new content.
Similar to other programming languages, functions are invoked via their name followed by parentheses (`()`).
```twig
{% verbatim %}{{ max(user.score, highscore) }}{% endverbatim %}
```
## Control Structure
Pebble provides several tags to control the flow of your template, two of the main ones being the {{ anchor('for') }} loop,
and {{ anchor('if') }} statements.
```twig
{% verbatim %}
{% for article in articles %}
{% endif %}
{%- endverbatim %}
```
## Including other Templates
The {{ anchor('include') }} tag is used to include the rendered output of one template into another.
```twig
{% verbatim %}
{% include "advertisement.html" %}
{%- endverbatim %}
```
## Template Inheritance
Template inheritance is the most powerful feature of Pebble. It allows templates to override sections of their parent
template. In your parent template you define "blocks" which are the sections that are allowed to be overriden.
First let us look at an example of a parent template:
```twig
{% verbatim %}
{% block title %}My Website{% endblock %}
{% block content %}{% endblock %}
{% endverbatim %}
```
In the above example, we have used the {{ anchor('block') }} tag to define several sections that child templates are
allowed to override.
A child template might look like this:
```twig
{% verbatim %}
{% extends "parent.html" %}
{% block title %} Home {% endblock %}
{% block content %}
Home
Welcome to my home page.
{% endblock %}
{%- endverbatim %}
```
The first line uses the {{ anchor('extends') }} tag to declare the parent template. The extends tag should be the
first tag in the template and there can only be one.
Evaluating the child template will produce the following output:
```twig
Home
Home
Welcome to my home page.
```
You may have noticed that in the above example, because the child template doesn't override the `footer` block,
the value from the parent is used instead.
Dynamic inheritance is possible by using an expression with the `extends` tag:
```twig
{% verbatim %}{% extends ajax ? 'ajax.html' : 'base.html' %}{% endverbatim %}
```
## Macros
Macros are lightweight and reusable template fragments. A macro is defined via the {{ anchor('macro') }} tag:
```twig
{% verbatim %}
{% macro input(type, name) %}
{% endmacro %}
{% endverbatim %}
```
And the macro will be invoked just like a function:
```twig
{% verbatim %}{{ input("text", "name", "Mitchell") }}{% endverbatim %}
```
Child templates will have access to macros defined in a parent template. To use macros located in a completely
different template, you can use the {{ anchor('import') }}) tag. A macro does not have access to the main context; the
only variables it can access are it's local arguments.
## Named Arguments
Using named arguments allows you to be more explicit with the values you are passing to a filter, function, test or
macro. They also allow you to avoid specifying arguments for which you don't want to change the default value.
```twig
{% verbatim %}{{ stringDate | date(existingFormat="yyyy-MMMM-d", format="yyyy/MMMM/d") }}{% endverbatim %}
```
Positional arguments can be used in conjunction with named arguments but all positional arguments must come before
any named arguments:
```twig
{% verbatim %}{{ stringDate | date("yyyy/MMMM/d", existingFormat="yyyy-MMMM-d") }}{% endverbatim %}
```
Macros are a great use case for named arguments because they also allow you to define default values for
unused arguments:
```twig
{% verbatim %}
{% macro input(type="text", name, value) %}
{% endmacro %}
{{ input(name="country") }}
{# will output: #}
{%- endverbatim %}
```
## Escaping
[XSS vulnerabilites](https://en.wikipedia.org/wiki/Cross-site_scripting) are the most common types of security
vulnerabilities in web applications and in order to avoid them you must escape potentially unsafe data before
presenting it to the end user. Pebble provides autoescaping of all such data which is enabled by default.
Autoescaping can be turned off, in which case Pebble provides an escape filter for more fine-grained manual escaping.
The following is an example of how autoescaping will escape your context variables:
```twig
{% verbatim %}
{% set danger = " " %}
{{ danger }}
{# will output: <br> #}
{%- endverbatim %}
```
If autoescaping is disabled you can still use the {{ anchor('escape') }} filter to aid with manual escaping:
```twig
{% verbatim %}
{% set danger = " " %}
{{ danger | escape }}
{# will output: <br> #}
{%- endverbatim %}
```
By default, the autoescaping mechanism and the escape filter assume that it is escaping within an HTML context.
You may want to use an alternate escaping strategy depending on the context:
```twig
{% verbatim %}
{% set danger = "alert(...)" %}
{%- endverbatim %}
```
See the {{ anchor('escaping guide', 'Escaping') }} for more information on how autoescaping works, how to disable it, and the various
escaping strategies that are available.
## Whitespace
The first newline after a pebble tag is automatically ignored; all other whitespace is ignored by Pebble and will be
included in the rendered output.
Pebble provides a whitespace control modifier to trim leading or trailing whitespace adjacent to any pebble tag.
```twig
{% verbatim %}
{{- "no whitespace" -}}
{# output: "
no whitespace
" #}
{%- endverbatim %}
```
It is also possible to only use the modifier on one side of the tag:
```twig
{% verbatim %}
{{- "no leading whitespace" }}
{# output: "
no whitespace
" #}
{%- endverbatim %}
```
## Comments
You can comment out any part of the template using the `{# ... #}` delimiters. These comments will not appear in
the rendered output.
```twig
{% verbatim %}
{# THIS IS A COMMENT #}
{% for article in articles %}
{{ article.title }}
{{ article.content }}
{% endfor %}
{%- endverbatim %}
```
## Expressions
Expressions in a Pebble template are very similar to expressions found in Java.
### Literals
The simplest form of expressions are literals. Literals are representations for Java types such as strings and numbers.
- `"Hello World"`: Everything between two double or single quotes is a string. You can use a backslash to escape
quotation marks within the string.
- `"Hello #{who}"`: String interpolation is also possible using `#{}` inside quotes. In this example,
if the value of the variable `who` is `"world"`, then the expression will be evaluated to `"Hello world"`.
- `100 + 10l * 2.5`: Integers, longs and floating point numbers are similar to their Java counterparts.
- `true` / `false`: Boolean values equivalent to their Java counterparts.
- `null`: Represents no specific value, similar to it's Java counterpart. `none` is an alias for null.
### Collections
Both lists and maps can be created directly within the template.
- `["apple", "banana", "pear"]`: A list of strings
- `{"apple":"red", "banana":"yellow", "pear":"green"}`: A map of strings
The collections can contain expressions.
### Math
Pebble allows you to calculate values using some basic mathematical operators. The following operators are supported:
- `+`: Addition
- `-`: Subtraction
- `/`: Division
- `%`: Modulus
- `*`: Multiplication
### Logic
You can combine multiple expressions with the following operators:
- `and`: Returns true if both operands are true
- `or`: Returns true if either operand is true
- `not`: Negates an expression
- `(...)`: Groups expressions together
### Comparisons
The following comparison operators are supported in any expression: `==`, `!=`, `<`, `>`, `>=`, and `<=`.
```twig
{% verbatim %}
{% if user.age >= 18 %}
...
{% endif %}
{%- endverbatim %}
```
### Tests
The `is` operator performs tests. Tests can be used to test an expression for certain qualities.
The right operand is the name of the test:
```twig
{% verbatim %}
{% if 3 is odd %}
...
{% endif %}
{%- endverbatim %}
```
Tests can be negated by using the is not operator:
```twig
{% verbatim %}
{% if name is not null %}
...
{% endif %}
{%- endverbatim %}
```
### Conditional (Ternary) Operator
The conditional operator is similar to its Java counterpart:
```twig
{% verbatim %}{{ foo ? "yes" : "no" }}{% endverbatim %}
```
### Operator Precedence
In order from highest to lowest precedence:
- `.`
- `|`
- `%`, `/`, `*`
- `-`, `+`
- `==`, `!=`, `>`, `<`, `>=`, `<=`
- `is`, `is not`
- `and`
- `or`
### Limiting the size of the rendered output
In case you’re running Pebble with templates provided by someone else, there’s an attack similar to
[zip bombs](https://en.wikipedia.org/wiki/Zip_bomb) or [XML bombs](https://en.wikipedia.org/wiki/Billion_laughs_attack)
that might cause your process to run out of memory. To protect against it, you can limit the size of the output when
evaluating a template:
```java
PebbleEngine pebble = new PebbleEngine.Builder()
// Output should not exceed 10 MB.
.maxRenderedSize(10 * 1024 * 1024)
.build();
```
This will throw a `PebbleException` when a template evaluation tries to write more characters than the limit you set.
### IDE's plugin
If 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.
================================================
FILE: docs/src/orchid/resources/wiki/guide/customize-defaults.md
================================================
---
---
Pebble 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.
In 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.
### Opt-Out using ExtensionCustomizer
The `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.
The following example removes the `ForTokenParser`, i.e. the ability to parse `{% for %}{{ ... }}{% endfor %}` constructs:
```java
class ExampleOptOuts extends ExtensionCustomizer {
public ExampleOptOuts(Extension ext) {
super(ext);
}
@Override
public List getTokenParsers() {
List tokenParsers = Optional.ofNullable(super.getTokenParsers())
.map(ArrayList::new).orElseGet(ArrayList::new);
tokenParsers.removeIf(x -> x instanceof ForTokenParser);
return tokenParsers;
}
}
```
The `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`:
```java
PebbleEngine engine = new PebbleEngine.Builder().registerExtensionCustomizer(ExampleOptOuts::new).build();
```
### Default implementation of ExtensionCustomizer
The `DisallowExtensionCustomizerBuilder` class can be used to disallow some default functionality, make pebble more controllable.
For example of use, see below:
```java
PebbleEngine engine = new PebbleEngine.Builder()
.registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder()
.disallowedTokenParserTags(singletonList("flush"))
.disallowedFunctionKeys(singletonList("max"))
.disallowedFilterKeys(singletonList("upper"))
.disallowedTestKeys(singletonList("null"))
.disallowedBinaryOperatorSymbols(singletonList(">"))
.disallowedUnaryOperatorSymbols(singletonList("-"))
.build())
.build();
```
================================================
FILE: docs/src/orchid/resources/wiki/guide/escaping.md
================================================
---
---
# Escaping
## Overview
[XSS vulnerabilites](https://en.wikipedia.org/wiki/Cross-site_scripting) are the most common types of security
vulnerabilities in web applications and in order to avoid them you must escape potentially unsafe data before
presenting it to the end user. Pebble provides autoescaping of all such data which is enabled by default.
Autoescaping can be turned off, in which case Pebble provides an {{ anchor('escape') }} filter for more
fine-grained manual escaping.
## Autoescaping
Autoescaping, which is enabled by default, will automatically escape the outcome of expressions
contained within print delimiters, i.e. {% verbatim %}`{{` and `}}`{% endverbatim %}:
```twig
{% verbatim %}
{% set danger = " " %}
{{ danger }}
{# will output: <br> #}
{%- endverbatim %}
```
The {{ anchor('raw') }} filter can be used to prevent the autoescaper from escaping a particular expression. It is
important that the raw filter is the last operation performed in the expression.
```twig
{% verbatim %}
{% set danger = " " %}
{{ danger | raw }}
{# will output: #}
{%- endverbatim %}
```
If the raw filter is not the last operation performed within the expression, the expression will be deemed
as possibly unsafe by the autoescaper and will be escaped. For example:
```twig
{% verbatim %}
{% set danger = " " %}
{{ danger | raw | uppercase }}
{# will output: <BR> #}
{%- endverbatim %}
```
### Exceptions
There are a few exceptions where expressions are **not** automatically escaped:
- If the expression only contains a string literal, it is assumed to be safe. For example:
```twig
{% verbatim %}
{{ ' ' }}
{# will output: #}
{%- endverbatim %}
```
- 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:
```twig
{% verbatim %}
{% set danger = " " %}
{{ danger | uppercase | raw }}
{# will output: #}
{%- endverbatim %}
```
### Autoescape Tag
The {{ anchor('autoescape') }} tag can be used to temporarily disable/re-enable the autoescaper as well as
change the escaping strategy for a portion of the template.
```twig
{% verbatim %}
{{ danger }} {# will be escaped by default #}
{% autoescape false %}
{{ danger }} {# will not be escaped #}
{% endautoescape %}
{%- endverbatim %}
```
```twig
{% verbatim %}
{{ danger }} {# will use the "html" escaping strategy #}
{% autoescape "js" %}
{{ danger }} {# will use the "js" escaping strategy #}
{% endautoescape %}
{%- endverbatim %}
```
### Disabling Autoescaper
```java
PebbleEngine engine = new PebbleEngine.Builder().autoEscaping(false).build();
```
## Manual Escaping
If autoescaping is disabled you can still use the {{ anchor('escape') }} filter to aid with manual escaping:
```twig
{% verbatim %}
{% set danger = " " %}
{{ danger | escape }}
{# will output: <br> #}
{%- endverbatim %}
```
## Strategies
When escaping data it is crucial that you utilize the correct escaping strategy depending on the context of the data.
By default, the autoescaper and the `escape` filter assume that you are escaping HTML data.
I highly recommend reading the [OWASP Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)
to understand the significance of escaping context.
Pebble provides the following escaping strategies:
- html
- js
- css
- url_param
You can use the {{ anchor('autoescape') }} tag to temporarily change the strategy used by the autoescaper otherwise you can
change the globally used default strategy:
```java
PebbleEngine engine = new PebbleEngine.Builder().defaultEscapingStrategy("js").build();
```
The escape filter will also accept a strategy as an argument:
```js
{% verbatim %}
var username ="{{ user.name | escape(strategy="js") }}";
{%- endverbatim %}
```
### Custom Strategy
You can add a custom escaping strategy by implementing `EscapingStrategy` and adding it to the `EscaperExtension`:
```java
PebbleEngine engine = new PebbleEngine.Builder().addEscapingStrategy("custom", new CustomEscapingStrategy()).build();
```
================================================
FILE: docs/src/orchid/resources/wiki/guide/extending-pebble.md
================================================
---
---
# Extending Pebble
## Overview
Pebble was designed to be flexible and accomodate the requirements of any project. You can add your own tags,
functions, operators, filters, tests, and global variables. The majority of these are quite trivial to implement.
Begin by creating a class that implements `Extension`. For your own convenience, I recommend extending
`AbstractExtension` if you can. After implementing the required methods, register your extension with the `PebbleEngine`
before compiling any templates:
```java
PebbleEngine engine = new PebbleEngine.Builder().extension(new CustomExtension()).build();
```
## Filters
To create custom filters, implement the `getFilters()` method of your extension which will return a map of filter
names and their corresponding implementations. A filter implementation must implement the `Filter` interface.
The `Filter` interface requires two methods to be implemented, `getArgumentNames()` and `apply()`. The
`getArgumentNames()` method returns a list of Strings that define both the order and names of expected arguments. If
this method returns null or an empty list, the filter supports dynamic arguments. This means the user can pass any
number of arguments—either named (e.g., myFilter(param="value")) or positional (e.g., myFilter("value"))—and they will be available in the arguments map.
The `apply` method is the actual filter implementation. Here's the parameters definition.
| Parameter name | Description |
| --- | --- |
| input | the data to be filtered |
| args | the map of arguments the user may have provided |
| self | An instance of `PebbleTemplate` which can be used to retrieve the template name for example |
| context | An instance of `EvaluationContext` which can be used to retrieve the locale for example|
| lineNumber | Useful when throwing exception to provide line number |
Because Pebble is dynamically typed, you will have to downcast the arguments to the expected type.
Here is an example of how the {{ anchor('upper') }} filter might be implemented:
```java
public class UpperFilter implements Filter {
@Override
public List getArgumentNames() {
return null;
}
@Override
public Object apply(Object input, Map args, PebbleTemplate self, EvaluationContext context, int lineNumber){
if(input == null){
return null;
}
if (input instanceof String) {
return ((String) input).toUpperCase(context.getLocale());
} else {
return input.toString().toUpperCase(context.getLocale());
}
}
}
```
## Tests
Adding custom tests is very similar to custom filters. Implement the `getTests()` method within your
extension which will return a map of test names and their corresponding implementations. A test
implementation will implement the `Test` interface. The `Test` interface is exactly like the `Filter`
interface except the apply method returns a boolean instead of an arbitrary object of any type.
Here is an example of how the {{ anchor('even') }} test might be implemented:
```java
public class EvenTest implements Test {
@Override
public List getArgumentNames() {
return null;
}
@Override
public boolean apply(Object input, Map args, PebbleTemplate self, EvaluationContext context, int lineNumber){
if (input == null) {
throw new PebbleException(null, "Can not pass null value to \"even\" test.", lineNumber, self.getName());
}
if (input instanceof Integer) {
return ((Integer) input) % 2 == 0;
} else {
return ((Long) input) % 2 == 0;
}
}
}
```
## Functions
Adding functions is also very similar to custom filters. First and foremost, it's important to
understand the different intentions behind a function and a filter because it can often be ambiguous
which one should be implemented. A filter is intended to modify existing content where a function is
moreso intended to produce new content.
To add functions, implement the `getFunctions()` method within your extension which will return a map of function
names and their corresponding implementations. A function implementation will implement the `Function` interface.
The `Function` interface is very similar to the `Filter` and `Test` interfaces.
Here is an example of how a fictional `fibonacciString` function might be implemented:
```java
public class FibonnaciStringFunction implements Function {
@Override
public List getArgumentNames() {
List names = new ArrayList<>();
names.add("length");
return names;
}
@Override
public Object execute(Map args, PebbleTemplate self, EvaluationContext context, int lineNumber) {
Integer length = (Integer)args.get("length");
Integer prev1 = 0;
Integer prev2 = 1;
StringBuilder result = new StringBuilder();
result.append("01");
for(int i = 2; i < length; i++){
Integer next = prev1 + prev2;
result.append(next);
prev1 = prev2;
prev2 = next;
}
return result.toString();
}
}
```
## Positional and Named Arguments
For filters, tests, and functions it is required that you implement the `getArgumentNames` method even if it
returns null. Returning a list of strings will allow the end user to call your filter/test/function using
named arguments. If this method returns null or an empty list, the function supports dynamic arguments. This means
the user can pass any number of arguments—either named (e.g., myFunction(param="value")) or positional
(e.g., myFunction("value"))—and they will be available in the arguments map.
Using the above fictional fibonacci function as an example, a user can invoke it in two different ways:
```twig
{% verbatim %}
{{ fibonacci(10) }}
{{ fibonacci(length=10) }}
{%- endverbatim %}
```
If the end user excludes the names and only uses positional arguments, the argument values will still end up
be mapped to the proper names when it's time to invoke the function's execute method. Your function implementation
doesn't have to worry whether the user used positional or named arguments. It is important though that if the
filter/function/test expects more than one argument, then the developer must communicate to the user the expected
order of arguments in the chance that the user wants to invoke it without using names.
Some functions such as the built in `min` and `max` functions accept an unlimited amount of arguments.
For this to happen, your function must not accept any named arguments (i.e. your `getArgumentNames` method
will return null or empty) and your `execute`` method will simply iterate over the values of the user provided
argument map while ignoring the keys of that map (Pebble will use arbitrary keys if there are no names to map to).
## Global Variables
Adding global variables, which are variables that are accessbile to all templates, is very trivial.
In your custom extension, implement the `getGlobalVariables()` method which returns a `Map`.
The contents of this map will be merged into the context you provide to each template at the time of rendering.
## Operators
Operators are more complex to implement than filters or tests. To add custom operators, implement the
`getBinaryOperators()` or the `getUnaryOperators()` method in your extension, or both. These methods return a
list of `BinaryOperator` or `UnaryOperator` objects, respectively.
Binary operators require the following information:
- Precedence: an integer relative to other operators which defines the order of operations.
- Symbol: a String representing the actual operator. This is typically a single character but doesn't have to be.
- Expression Class: A class that extends `BinaryExpression`. This class will perform the actual operator implementation.
- Associativity: Either left or right depending on how the operator is used.
A unary operator is much the same except it's expression class must extend `UnaryExpression` and there is no associativity.
The precedence values for existing core operators are as followed:
- `or`: 10
- `and`: 15
- `is`: 20
- `is not`: 20
- `==`: 30
- `!=`: 30
- `>`: 30
- `<`: 30
- `>=`: 30
- `<=`: 30
- `+`: 40
- `-`: 40
- `not`: 50 (Unary)
- `*`: 60
- `/`: 60
- `%`: 60
- `|`: 100
- `+`: 500 (Unary)
- `-`: 500 (Unary)
The following is an example of how the addition operator (`+`) might have been implemented:
```java
public class AdditionOperator implements BinaryOperator {
public int getPrecedence(){
return 30;
}
public String getSymbol(){
return "+";
}
public BinaryExpression> createInstance() {
return new AddExpression();
}
public BinaryOperatorType getType() {
return BinaryOperatorType.NORMAL;
}
public Associativity getAssociativity(){
return Associativity.LEFT;
}
}
```
Alongside each operator class you will also need to implement a corresponding `BinaryExpression` class
which actually implements the operator. The above example references a fictional `AdditionExpression` class
which might look like the following:
```java
public class AdditionExpression extends BinaryExpression