Fix issues from Inheritance spec (#36)

* Separate inheritance block and expansion

* Catch top level partial definition, and block newlines

* Add testTrailingNewLines to verify output of trailing newlines in partials

* Remove comment

* If block,partial has indentation add indent for first line

* Re-enable full sections spec

* withBlockExpansion

* Get indentation of blocks correct
This commit is contained in:
Adam Fowler
2024-07-15 09:36:15 +01:00
committed by GitHub
parent cc0eaffa06
commit 7689de0a42
6 changed files with 215 additions and 45 deletions

View File

@@ -87,6 +87,23 @@ struct MustacheContext {
)
}
/// return context with indent information for invoking an inheritance block
func withBlockExpansion(indented: String?) -> MustacheContext {
let indentation: String? = if let indented {
(self.indentation ?? "") + indented
} else {
self.indentation
}
return .init(
stack: self.stack,
sequenceContext: nil,
indentation: indentation,
inherited: self.inherited,
contentType: self.contentType,
library: self.library
)
}
/// return context with sequence info and sequence element added to stack
func withSequence(_ object: Any, sequenceContext: MustacheSequenceContext) -> MustacheContext {
var stack = self.stack

View File

@@ -42,23 +42,58 @@ extension MustacheTemplate {
}
struct ParserState {
struct Flags: OptionSet {
let rawValue: Int
init(rawValue: Int) {
self.rawValue = rawValue
}
static var newLine: Self { .init(rawValue: 1 << 0) }
static var isPartialDefinition: Self { .init(rawValue: 1 << 1) }
static var isPartialDefinitionTopLevel: Self { .init(rawValue: 1 << 2) }
}
var sectionName: String?
var sectionTransforms: [String] = []
var newLine: Bool
var flags: Flags
var startDelimiter: String
var endDelimiter: String
var partialDefinitionIndent: Substring?
var newLine: Bool {
get { self.flags.contains(.newLine) }
set {
if newValue {
self.flags.insert(.newLine)
} else {
self.flags.remove(.newLine)
}
}
}
init() {
self.sectionName = nil
self.newLine = true
self.flags = .newLine
self.startDelimiter = "{{"
self.endDelimiter = "}}"
}
func withSectionName(_ name: String, transforms: [String] = []) -> ParserState {
func withSectionName(_ name: String, newLine: Bool, transforms: [String] = []) -> ParserState {
var newValue = self
newValue.sectionName = name
newValue.sectionTransforms = transforms
newValue.flags.remove(.isPartialDefinitionTopLevel)
if !newLine {
newValue.flags.remove(.newLine)
}
return newValue
}
func withInheritancePartial(_ name: String) -> ParserState {
var newValue = self
newValue.sectionName = name
newValue.flags.insert([.newLine, .isPartialDefinition, .isPartialDefinitionTopLevel])
return newValue
}
@@ -66,6 +101,7 @@ extension MustacheTemplate {
var newValue = self
newValue.startDelimiter = start
newValue.endDelimiter = end
newValue.flags.remove(.isPartialDefinitionTopLevel)
return newValue
}
}
@@ -88,7 +124,19 @@ extension MustacheTemplate {
while !parser.reachedEnd() {
// if new line read whitespace
if state.newLine {
whiteSpaceBefore = parser.read(while: Set(" \t"))
let whiteSpace = parser.read(while: Set(" \t"))
// If inside a partial block definition
if state.flags.contains(.isPartialDefinition), !state.flags.contains(.isPartialDefinitionTopLevel) {
// if definition indent has been set then remove it from current whitespace otherwise set the
// indent as this is the first line of the partial definition
if let partialDefinitionIndent = state.partialDefinitionIndent {
whiteSpaceBefore = whiteSpace.dropFirst(partialDefinitionIndent.count)
} else {
state.partialDefinitionIndent = whiteSpace
}
} else {
whiteSpaceBefore = whiteSpace
}
}
let text = try readUntilDelimiterOrNewline(&parser, state: state)
// if we hit a newline add text
@@ -121,7 +169,7 @@ extension MustacheTemplate {
tokens.append(.text(String(whiteSpaceBefore)))
whiteSpaceBefore = ""
}
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transforms: transforms))
let sectionTokens = try parse(&parser, state: state.withSectionName(name, newLine: setNewLine, transforms: transforms))
tokens.append(.section(name: name, transforms: transforms, template: MustacheTemplate(sectionTokens)))
case "^":
@@ -134,24 +182,9 @@ extension MustacheTemplate {
tokens.append(.text(String(whiteSpaceBefore)))
whiteSpaceBefore = ""
}
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transforms: transforms))
let sectionTokens = try parse(&parser, state: state.withSectionName(name, newLine: setNewLine, transforms: transforms))
tokens.append(.invertedSection(name: name, transforms: transforms, template: MustacheTemplate(sectionTokens)))
case "$":
// inherited section
parser.unsafeAdvance()
let (name, transforms) = try parseName(&parser, state: state)
// ERROR: can't have transform applied to inherited sections
guard transforms.isEmpty else { throw Error.transformAppliedToInheritanceSection }
if self.isStandalone(&parser, state: state) {
setNewLine = true
} else if whiteSpaceBefore.count > 0 {
tokens.append(.text(String(whiteSpaceBefore)))
whiteSpaceBefore = ""
}
let sectionTokens = try parse(&parser, state: state.withSectionName(name, transforms: transforms))
tokens.append(.inheritedSection(name: name, template: MustacheTemplate(sectionTokens)))
case "/":
// end of section
parser.unsafeAdvance()
@@ -215,20 +248,18 @@ extension MustacheTemplate {
// partial with inheritance
parser.unsafeAdvance()
let name = try parsePartialName(&parser, state: state)
var indent: String?
if whiteSpaceBefore.count > 0 {
tokens.append(.text(String(whiteSpaceBefore)))
}
if self.isStandalone(&parser, state: state) {
setNewLine = true
} else if whiteSpaceBefore.count > 0 {
indent = String(whiteSpaceBefore)
tokens.append(.text(indent!))
whiteSpaceBefore = ""
}
let sectionTokens = try parse(&parser, state: state.withSectionName(name))
let sectionTokens = try parse(&parser, state: state.withInheritancePartial(name))
var inherit: [String: MustacheTemplate] = [:]
// parse tokens in section to extract inherited sections
for token in sectionTokens {
switch token {
case .inheritedSection(let name, let template):
case .blockDefinition(let name, let template):
inherit[name] = template
case .text:
break
@@ -236,7 +267,34 @@ extension MustacheTemplate {
throw Error.illegalTokenInsideInheritSection
}
}
tokens.append(.partial(name, indentation: indent, inherits: inherit))
tokens.append(.partial(name, indentation: String(whiteSpaceBefore), inherits: inherit))
whiteSpaceBefore = ""
case "$":
// inherited section
parser.unsafeAdvance()
let (name, transforms) = try parseName(&parser, state: state)
// ERROR: can't have transforms applied to inherited sections
guard transforms.isEmpty else { throw Error.transformAppliedToInheritanceSection }
if state.flags.contains(.isPartialDefinitionTopLevel) {
let standAlone = self.isStandalone(&parser, state: state)
if standAlone {
setNewLine = true
}
let sectionTokens = try parse(&parser, state: state.withSectionName(name, newLine: setNewLine))
tokens.append(.blockDefinition(name: name, template: MustacheTemplate(sectionTokens)))
} else {
if whiteSpaceBefore.count > 0 {
tokens.append(.text(String(whiteSpaceBefore)))
}
if self.isStandalone(&parser, state: state) {
setNewLine = true
} else if whiteSpaceBefore.count > 0 {}
let sectionTokens = try parse(&parser, state: state.withSectionName(name, newLine: setNewLine))
tokens.append(.blockExpansion(name: name, default: MustacheTemplate(sectionTokens), indentation: String(whiteSpaceBefore)))
whiteSpaceBefore = ""
}
case "=":
// set delimiter

View File

@@ -28,7 +28,8 @@ extension MustacheTemplate {
if let indentation = context.indentation, indentation != "" {
for token in tokens {
let renderedString = self.renderToken(token, context: &context)
if renderedString != "", string.last == "\n" {
// if rendered string is not empty and we are on a new line
if renderedString.count > 0, string.last == "\n" {
string += indentation
}
string += renderedString
@@ -75,11 +76,11 @@ extension MustacheTemplate {
let child = self.getChild(named: variable, transforms: transforms, context: context)
return self.renderInvertedSection(child, with: template, context: context)
case .inheritedSection(let name, let template):
case .blockExpansion(let name, let defaultTemplate, let indented):
if let override = context.inherited?[name] {
return override.render(context: context)
return override.render(context: context.withBlockExpansion(indented: indented))
} else {
return template.render(context: context)
return defaultTemplate.render(context: context.withBlockExpansion(indented: indented))
}
case .partial(let name, let indentation, let overrides):
@@ -89,6 +90,9 @@ extension MustacheTemplate {
case .contentType(let contentType):
context = context.withContentType(contentType)
case .blockDefinition:
fatalError("Should not be rendering block definitions")
}
return ""
}

View File

@@ -38,7 +38,8 @@ public struct MustacheTemplate: Sendable {
case unescapedVariable(name: String, transforms: [String] = [])
case section(name: String, transforms: [String] = [], template: MustacheTemplate)
case invertedSection(name: String, transforms: [String] = [], template: MustacheTemplate)
case inheritedSection(name: String, template: MustacheTemplate)
case blockDefinition(name: String, template: MustacheTemplate)
case blockExpansion(name: String, default: MustacheTemplate, indentation: String?)
case partial(String, indentation: String?, inherits: [String: MustacheTemplate]?)
case contentType(MustacheContentType)
}