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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user