Update from Hummingbird Project Template (#58)

* Update from hummingbird-project-template 572d468b2cabeca286314c5a35196bd42445c8ef

* run swift-format

* Remove .swiftformat

---------

Co-authored-by: adam-fowler <adam-fowler@users.noreply.github.com>
Co-authored-by: Adam Fowler <adamfowler71@gmail.com>
This commit is contained in:
hummingbird-automation[bot]
2024-11-28 07:31:09 +00:00
committed by GitHub
parent 8c5c8ead74
commit ec4ef9aa04
21 changed files with 663 additions and 430 deletions

View File

@@ -8,16 +8,12 @@ concurrency:
jobs:
validate:
runs-on: macOS-latest
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install Dependencies
run: |
brew install mint
mint install NickLockwood/SwiftFormat@0.53.10 --no-link
- name: run script
run: ./scripts/validate.sh

63
.swift-format Normal file
View File

@@ -0,0 +1,63 @@
{
"version" : 1,
"indentation" : {
"spaces" : 4
},
"tabWidth" : 4,
"fileScopedDeclarationPrivacy" : {
"accessLevel" : "private"
},
"spacesAroundRangeFormationOperators" : false,
"indentConditionalCompilationBlocks" : false,
"indentSwitchCaseLabels" : false,
"lineBreakAroundMultilineExpressionChainComponents" : false,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : true,
"lineBreakBeforeEachGenericRequirement" : true,
"lineLength" : 150,
"maximumBlankLines" : 1,
"respectsExistingLineBreaks" : true,
"prioritizeKeepingFunctionOutputTogether" : true,
"multiElementCollectionTrailingCommas" : true,
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : false,
"AlwaysUseLiteralForEmptyCollectionInit" : false,
"AlwaysUseLowerCamelCase" : false,
"AmbiguousTrailingClosureOverload" : true,
"BeginDocumentationCommentWithOneLineSummary" : false,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FileScopedDeclarationPrivacy" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"NeverForceUnwrap" : false,
"NeverUseForceTry" : false,
"NeverUseImplicitlyUnwrappedOptionals" : false,
"NoAccessLevelOnExtensionDeclaration" : true,
"NoAssignmentInExpressions" : true,
"NoBlockComments" : true,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : false,
"NoParensAroundConditions" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OmitExplicitReturns" : true,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReplaceForEachWithForLoop" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"UseEarlyExits" : false,
"UseExplicitNilCheckInConditions" : false,
"UseLetInEveryBoundCaseVariable" : false,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : false,
"UseSynthesizedInitializer" : false,
"UseTripleSlashForDocumentationComments" : true,
"UseWhereClausesInForLoops" : false,
"ValidateDocumentationComments" : false
}
}

View File

@@ -1,26 +0,0 @@
# Minimum swiftformat version
--minversion 0.53.10
# Swift version
--swiftversion 5.9
# file options
--exclude .build
# rules
--disable redundantReturn, extensionAccessControl, typeSugar, conditionalAssignment, preferForLoop, redundantInternal, redundantStaticSelf
# format options
--ifdef no-indent
--nospaceoperators ...,..<
--patternlet inline
--self insert
--stripunusedargs unnamed-only
#--maxwidth 150
--wraparguments before-first
--wrapparameters before-first
--wrapcollections before-first
#file header
# --header "//===----------------------------------------------------------------------===//\n//\n// This source file is part of the Hummingbird server framework project\n//\n// Copyright (c) {created.year}-{year} the Hummingbird authors\n// Licensed under Apache License v2.0\n//\n// See LICENSE.txt for license information\n// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors\n//\n// SPDX-License-Identifier: Apache-2.0\n//\n//===----------------------------------------------------------------------===//"

View File

@@ -28,4 +28,4 @@ The main development branch of the repository is `main`.
### Formatting
We use Nick Lockwood's SwiftFormat for formatting code. PRs will not be accepted if they haven't be formatted. The current version of SwiftFormat we are using is v0.53.10.
We use Apple's swift-format for formatting code. PRs will not be accepted if they haven't be formatted.

View File

@@ -7,7 +7,7 @@ let package = Package(
name: "swift-mustache",
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)],
products: [
.library(name: "Mustache", targets: ["Mustache"]),
.library(name: "Mustache", targets: ["Mustache"])
],
dependencies: [],
targets: [

View File

@@ -21,14 +21,14 @@ public protocol MustacheContentType: Sendable {
/// Text content type where no character is escaped
struct TextContentType: MustacheContentType {
func escapeText(_ text: String) -> String {
return text
text
}
}
/// HTML content where text is escaped for HTML output
struct HTMLContentType: MustacheContentType {
func escapeText(_ text: String) -> String {
return text.htmlEscape()
text.htmlEscape()
}
}
@@ -39,7 +39,7 @@ struct HTMLContentType: MustacheContentType {
/// with `MustacheContentTypes.register`.
public enum MustacheContentTypes {
static func get(_ name: String) -> MustacheContentType? {
return self.types[name]
self.types[name]
}
/// Register new content type

View File

@@ -131,7 +131,7 @@ struct MustacheContext {
/// return context with sequence info and sequence element added to stack
func withContentType(_ contentType: MustacheContentType) -> MustacheContext {
return .init(
.init(
stack: self.stack,
sequenceContext: self.sequenceContext,
indentation: self.indentation,

View File

@@ -50,6 +50,6 @@ public struct MustacheLambda {
}
internal func callAsFunction(_ s: String) -> Any? {
return self.callback(s)
self.callback(s)
}
}

View File

@@ -21,5 +21,5 @@ public protocol MustacheParent {
/// Extend dictionary where the key is a string so that it uses the key values to access
/// it values
extension Dictionary: MustacheParent where Key == String {
public func child(named: String) -> Any? { return self[named] }
public func child(named: String) -> Any? { self[named] }
}

View File

@@ -38,7 +38,7 @@ struct Parser {
self.position = string.startIndex
}
var buffer: String { return self._storage.buffer }
var buffer: String { self._storage.buffer }
private(set) var position: String.Index
}
@@ -59,7 +59,10 @@ extension Parser {
/// - Returns: If current character was the one we expected
mutating func read(_ char: Character) throws -> Bool {
let c = try character()
guard c == char else { unsafeRetreat(); return false }
guard c == char else {
unsafeRetreat()
return false
}
return true
}
@@ -84,7 +87,10 @@ extension Parser {
/// - Returns: If current character is in character set
mutating func read(_ characterSet: Set<Character>) throws -> Bool {
let c = try character()
guard characterSet.contains(c) else { unsafeRetreat(); return false }
guard characterSet.contains(c) else {
unsafeRetreat()
return false
}
return true
}
@@ -236,7 +242,7 @@ extension Parser {
@discardableResult mutating func read(while: Character) -> Int {
var count = 0
while !self.reachedEnd(),
unsafeCurrent() == `while`
unsafeCurrent() == `while`
{
unsafeAdvance()
count += 1
@@ -250,7 +256,7 @@ extension Parser {
@discardableResult mutating func read(while keyPath: KeyPath<Character, Bool>) -> Substring {
let startIndex = self.position
while !self.reachedEnd(),
unsafeCurrent()[keyPath: keyPath]
unsafeCurrent()[keyPath: keyPath]
{
unsafeAdvance()
}
@@ -263,7 +269,7 @@ extension Parser {
@discardableResult mutating func read(while cb: (Character) -> Bool) -> Substring {
let startIndex = self.position
while !self.reachedEnd(),
cb(unsafeCurrent())
cb(unsafeCurrent())
{
unsafeAdvance()
}
@@ -276,7 +282,7 @@ extension Parser {
@discardableResult mutating func read(while characterSet: Set<Character>) -> Substring {
let startIndex = self.position
while !self.reachedEnd(),
characterSet.contains(unsafeCurrent())
characterSet.contains(unsafeCurrent())
{
unsafeAdvance()
}
@@ -286,13 +292,13 @@ extension Parser {
/// Return whether we have reached the end of the buffer
/// - Returns: Have we reached the end
func reachedEnd() -> Bool {
return self.position == self.buffer.endIndex
self.position == self.buffer.endIndex
}
/// Return whether we are at the start of the buffer
/// - Returns: Are we are the start
func atStart() -> Bool {
return self.position == self.buffer.startIndex
self.position == self.buffer.startIndex
}
}
@@ -378,7 +384,7 @@ extension Parser {
// unsafe versions without checks
extension Parser {
func unsafeCurrent() -> Character {
return self.buffer[self.position]
self.buffer[self.position]
}
mutating func unsafeAdvance() {

View File

@@ -327,7 +327,8 @@ extension MustacheTemplate {
}
if self.isStandalone(&parser, state: state) {
setNewLine = true
} else if whiteSpaceBefore.count > 0 {}
} else if whiteSpaceBefore.count > 0 {
}
let sectionTemplate = try parse(&parser, state: state.withSectionName(name, newLine: setNewLine))
tokens.append(.blockExpansion(name: name, default: sectionTemplate, indentation: String(whiteSpaceBefore)))
whiteSpaceBefore = ""
@@ -415,7 +416,7 @@ extension MustacheTemplate {
nameParser.unsafeAdvance()
// We need to have a `)` for each transform that we've parsed
guard nameParser.read(while: ")") + 1 == existing.count,
nameParser.reachedEnd()
nameParser.reachedEnd()
else {
throw Error.unfinishedName
}
@@ -512,7 +513,7 @@ extension MustacheTemplate {
}
static func isStandalone(_ parser: inout Parser, state: ParserState) -> Bool {
return state.newLine && self.hasLineFinished(&parser)
state.newLine && self.hasLineFinished(&parser)
}
private static let sectionNameCharsWithoutBrackets = Set<Character>("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_?*")

View File

@@ -285,7 +285,7 @@ extension MustacheTemplate {
// run transform on the current child
for transform in transforms.reversed() {
if let runnable = child as? MustacheTransformable,
let transformed = runnable.transform(transform)
let transformed = runnable.transform(transform)
{
child = transformed
continue

View File

@@ -31,13 +31,13 @@ public protocol MustacheTransformable {
func transform(_ name: String) -> Any?
}
public extension StringProtocol {
extension StringProtocol {
/// Transform String/Substring
///
/// Transforms available are `capitalized`, `lowercased`, `uppercased` and `reversed`
/// - Parameter name: transform name
/// - Returns: Result
func transform(_ name: String) -> Any? {
public func transform(_ name: String) -> Any? {
switch name {
case "empty":
return isEmpty
@@ -209,13 +209,13 @@ extension Dictionary: ComparableSequence where Key: Comparable {
}
}
public extension FixedWidthInteger {
extension FixedWidthInteger {
/// Transform FixedWidthInteger
///
/// Transforms available are `plusone`, `minusone`, `odd`, `even`
/// - Parameter name: transform name
/// - Returns: Result
func transform(_ name: String) -> Any? {
public func transform(_ name: String) -> Any? {
switch name {
case "equalzero":
return self == 0

View File

@@ -17,11 +17,15 @@ import XCTest
final class ErrorTests: XCTestCase {
func testSectionCloseNameIncorrect() {
XCTAssertThrowsError(try MustacheTemplate(string: """
{{#test}}
{{.}}
{{/test2}}
""")) { error in
XCTAssertThrowsError(
try MustacheTemplate(
string: """
{{#test}}
{{.}}
{{/test2}}
"""
)
) { error in
switch error {
case let error as MustacheTemplate.ParserError:
XCTAssertEqual(error.error as? MustacheTemplate.Error, .sectionCloseNameIncorrect)
@@ -36,11 +40,15 @@ final class ErrorTests: XCTestCase {
}
func testUnfinishedName() {
XCTAssertThrowsError(try MustacheTemplate(string: """
{{#test}}
{{name}
{{/test2}}
""")) { error in
XCTAssertThrowsError(
try MustacheTemplate(
string: """
{{#test}}
{{name}
{{/test2}}
"""
)
) { error in
switch error {
case let error as MustacheTemplate.ParserError:
XCTAssertEqual(error.error as? MustacheTemplate.Error, .unfinishedName)
@@ -55,10 +63,14 @@ final class ErrorTests: XCTestCase {
}
func testExpectedSectionEnd() {
XCTAssertThrowsError(try MustacheTemplate(string: """
{{#test}}
{{.}}
""")) { error in
XCTAssertThrowsError(
try MustacheTemplate(
string: """
{{#test}}
{{.}}
"""
)
) { error in
switch error {
case let error as MustacheTemplate.ParserError:
XCTAssertEqual(error.error as? MustacheTemplate.Error, .expectedSectionEnd)
@@ -73,11 +85,15 @@ final class ErrorTests: XCTestCase {
}
func testInvalidSetDelimiter() {
XCTAssertThrowsError(try MustacheTemplate(string: """
{{=<% %>=}}
<%.%>
<%={{}}=%>
""")) { error in
XCTAssertThrowsError(
try MustacheTemplate(
string: """
{{=<% %>=}}
<%.%>
<%={{}}=%>
"""
)
) { error in
switch error {
case let error as MustacheTemplate.ParserError:
XCTAssertEqual(error.error as? MustacheTemplate.Error, .invalidSetDelimiter)

View File

@@ -12,9 +12,10 @@
//
//===----------------------------------------------------------------------===//
@testable import Mustache
import XCTest
@testable import Mustache
final class LibraryTests: XCTestCase {
func testDirectoryLoad() async throws {
let fs = FileManager()
@@ -54,11 +55,13 @@ final class LibraryTests: XCTestCase {
let mustache = Data("<test>{{#value}}<value>{{.}}</value>{{/value}}</test>".utf8)
try mustache.write(to: URL(fileURLWithPath: "templates/test.mustache"))
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test.mustache")) }
let mustache2 = Data("""
{{#test}}
{{{name}}
{{/test2}}
""".utf8)
let mustache2 = Data(
"""
{{#test}}
{{{name}}
{{/test2}}
""".utf8
)
try mustache2.write(to: URL(fileURLWithPath: "templates/error.mustache"))
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates/error.mustache")) }

View File

@@ -12,120 +12,156 @@
//
//===----------------------------------------------------------------------===//
@testable import Mustache
import XCTest
@testable import Mustache
final class PartialTests: XCTestCase {
/// Testing partials
func testMustacheManualExample9() throws {
let template = try MustacheTemplate(string: """
<h2>Names</h2>
{{#names}}
{{> user}}
{{/names}}
""")
let template2 = try MustacheTemplate(string: """
<strong>{{.}}</strong>
let template = try MustacheTemplate(
string: """
<h2>Names</h2>
{{#names}}
{{> user}}
{{/names}}
"""
)
let template2 = try MustacheTemplate(
string: """
<strong>{{.}}</strong>
""")
"""
)
let library = MustacheLibrary(templates: ["base": template, "user": template2])
let object: [String: Any] = ["names": ["john", "adam", "claire"]]
XCTAssertEqual(library.render(object, withTemplate: "base"), """
<h2>Names</h2>
<strong>john</strong>
<strong>adam</strong>
<strong>claire</strong>
XCTAssertEqual(
library.render(object, withTemplate: "base"),
"""
<h2>Names</h2>
<strong>john</strong>
<strong>adam</strong>
<strong>claire</strong>
""")
"""
)
}
/// Test where last line of partial generates no content. It should not add a
/// tab either
func testPartialEmptyLineTabbing() throws {
let template = try MustacheTemplate(string: """
<h2>Names</h2>
{{#names}}
{{> user}}
{{/names}}
Text after
let template = try MustacheTemplate(
string: """
<h2>Names</h2>
{{#names}}
{{> user}}
{{/names}}
Text after
""")
let template2 = try MustacheTemplate(string: """
{{^empty(.)}}
<strong>{{.}}</strong>
{{/empty(.)}}
{{#empty(.)}}
<strong>empty</strong>
{{/empty(.)}}
"""
)
let template2 = try MustacheTemplate(
string: """
{{^empty(.)}}
<strong>{{.}}</strong>
{{/empty(.)}}
{{#empty(.)}}
<strong>empty</strong>
{{/empty(.)}}
""")
"""
)
var library = MustacheLibrary()
library.register(template, named: "base")
library.register(template2, named: "user")
let object: [String: Any] = ["names": ["john", "adam", "claire"]]
XCTAssertEqual(library.render(object, withTemplate: "base"), """
<h2>Names</h2>
<strong>john</strong>
<strong>adam</strong>
<strong>claire</strong>
Text after
XCTAssertEqual(
library.render(object, withTemplate: "base"),
"""
<h2>Names</h2>
<strong>john</strong>
<strong>adam</strong>
<strong>claire</strong>
Text after
""")
"""
)
}
func testTrailingNewLines() throws {
let template1 = try MustacheTemplate(string: """
{{> withNewLine }}
>> {{> withNewLine }}
[ {{> withNewLine }} ]
""")
let template2 = try MustacheTemplate(string: """
{{> withoutNewLine }}
>> {{> withoutNewLine }}
[ {{> withoutNewLine }} ]
""")
let withNewLine = try MustacheTemplate(string: """
{{#things}}{{.}}, {{/things}}
let template1 = try MustacheTemplate(
string: """
{{> withNewLine }}
>> {{> withNewLine }}
[ {{> withNewLine }} ]
"""
)
let template2 = try MustacheTemplate(
string: """
{{> withoutNewLine }}
>> {{> withoutNewLine }}
[ {{> withoutNewLine }} ]
"""
)
let withNewLine = try MustacheTemplate(
string: """
{{#things}}{{.}}, {{/things}}
""")
"""
)
let withoutNewLine = try MustacheTemplate(string: "{{#things}}{{.}}, {{/things}}")
let library = MustacheLibrary(templates: ["base1": template1, "base2": template2, "withNewLine": withNewLine, "withoutNewLine": withoutNewLine])
let library = MustacheLibrary(templates: [
"base1": template1, "base2": template2, "withNewLine": withNewLine, "withoutNewLine": withoutNewLine,
])
let object = ["things": [1, 2, 3, 4, 5]]
XCTAssertEqual(library.render(object, withTemplate: "base1"), """
1, 2, 3, 4, 5,
>> 1, 2, 3, 4, 5,
XCTAssertEqual(
library.render(object, withTemplate: "base1"),
"""
1, 2, 3, 4, 5,
>> 1, 2, 3, 4, 5,
[ 1, 2, 3, 4, 5,
]
""")
XCTAssertEqual(library.render(object, withTemplate: "base2"), """
1, 2, 3, 4, 5, >> 1, 2, 3, 4, 5,
[ 1, 2, 3, 4, 5, ]
""")
[ 1, 2, 3, 4, 5,
]
"""
)
XCTAssertEqual(
library.render(object, withTemplate: "base2"),
"""
1, 2, 3, 4, 5, >> 1, 2, 3, 4, 5,
[ 1, 2, 3, 4, 5, ]
"""
)
}
/// Testing dynamic partials
func testDynamicPartials() throws {
let template = try MustacheTemplate(string: """
<h2>Names</h2>
{{partial}}
""")
let template2 = try MustacheTemplate(string: """
{{#names}}
<strong>{{.}}</strong>
{{/names}}
""")
let template = try MustacheTemplate(
string: """
<h2>Names</h2>
{{partial}}
"""
)
let template2 = try MustacheTemplate(
string: """
{{#names}}
<strong>{{.}}</strong>
{{/names}}
"""
)
let library = MustacheLibrary(templates: ["base": template])
let object: [String: Any] = ["names": ["john", "adam", "claire"], "partial": template2]
XCTAssertEqual(library.render(object, withTemplate: "base"), """
<h2>Names</h2>
<strong>john</strong>
<strong>adam</strong>
<strong>claire</strong>
XCTAssertEqual(
library.render(object, withTemplate: "base"),
"""
<h2>Names</h2>
<strong>john</strong>
<strong>adam</strong>
<strong>claire</strong>
""")
"""
)
}
/// test inheritance
@@ -163,15 +199,18 @@ final class PartialTests: XCTestCase {
""",
named: "mypage"
)
XCTAssertEqual(library.render({}, withTemplate: "mypage")!, """
<html>
<head>
<title>My page title</title>
</head>
<h1>Hello world</h1>
</html>
XCTAssertEqual(
library.render({}, withTemplate: "mypage")!,
"""
<html>
<head>
<title>My page title</title>
</head>
<h1>Hello world</h1>
</html>
""")
"""
)
}
func testInheritanceIndentation() throws {
@@ -194,11 +233,14 @@ final class PartialTests: XCTestCase {
""",
named: "template"
)
XCTAssertEqual(library.render({}, withTemplate: "template"), """
Hi,
one
two
XCTAssertEqual(
library.render({}, withTemplate: "template"),
"""
Hi,
one
two
""")
"""
)
}
}

View File

@@ -50,7 +50,8 @@ extension AnyDecodable {
self.init(dictionary.mapValues { $0.value })
} else {
throw DecodingError.dataCorruptedError(
in: container, debugDescription: "AnyDecodable value cannot be decoded"
in: container,
debugDescription: "AnyDecodable value cannot be decoded"
)
}
}
@@ -102,7 +103,8 @@ final class MustacheSpecTests: XCTestCase {
\(result ?? "nil")
expected:
\(test.expected)
""")
"""
)
}
}
}
@@ -113,7 +115,8 @@ final class MustacheSpecTests: XCTestCase {
func testSpec(name: String, ignoring: [String] = []) async throws {
let url = URL(
string: "https://raw.githubusercontent.com/mustache/spec/master/specs/\(name).json")!
string: "https://raw.githubusercontent.com/mustache/spec/master/specs/\(name).json"
)!
try await testSpec(url: url, ignoring: ignoring)
}
@@ -135,7 +138,8 @@ final class MustacheSpecTests: XCTestCase {
func testSpec(name: String, only: [String]) async throws {
let url = URL(
string: "https://raw.githubusercontent.com/mustache/spec/master/specs/\(name).json")!
string: "https://raw.githubusercontent.com/mustache/spec/master/specs/\(name).json"
)!
try await testSpec(url: url, only: only)
}
@@ -161,7 +165,12 @@ final class MustacheSpecTests: XCTestCase {
"Interpolation": MustacheLambda { "world" },
"Interpolation - Expansion": MustacheLambda { "{{planet}}" },
"Interpolation - Alternate Delimiters": MustacheLambda { "|planet| => {{planet}}" },
"Interpolation - Multiple Calls": MustacheLambda { return MustacheLambda { g += 1; return g }},
"Interpolation - Multiple Calls": MustacheLambda {
return MustacheLambda {
g += 1
return g
}
},
"Escaping": MustacheLambda { ">" },
"Section": MustacheLambda { text in text == "{{x}}" ? "yes" : "no" },
"Section - Expansion": MustacheLambda { text in text + "{{planet}}" + text },

View File

@@ -12,9 +12,10 @@
//
//===----------------------------------------------------------------------===//
@testable import Mustache
import XCTest
@testable import Mustache
final class TemplateParserTests: XCTestCase {
func testText() throws {
let template = try MustacheTemplate(string: "test template")

View File

@@ -141,81 +141,103 @@ final class TemplateRendererTests: XCTestCase {
/// variables
func testMustacheManualVariables() throws {
let template = try MustacheTemplate(string: """
Hello {{name}}
You have just won {{value}} dollars!
{{#in_ca}}
Well, {{taxed_value}} dollars, after taxes.
{{/in_ca}}
""")
let template = try MustacheTemplate(
string: """
Hello {{name}}
You have just won {{value}} dollars!
{{#in_ca}}
Well, {{taxed_value}} dollars, after taxes.
{{/in_ca}}
"""
)
let object: [String: Any] = ["name": "Chris", "value": 10000, "taxed_value": 10000 - (10000 * 0.4), "in_ca": true]
XCTAssertEqual(template.render(object), """
Hello Chris
You have just won 10000 dollars!
Well, 6000.0 dollars, after taxes.
XCTAssertEqual(
template.render(object),
"""
Hello Chris
You have just won 10000 dollars!
Well, 6000.0 dollars, after taxes.
""")
"""
)
}
/// test escaped and unescaped text
func testMustacheManualEscapedText() throws {
let template = try MustacheTemplate(string: """
*{{name}}
*{{age}}
*{{company}}
*{{{company}}}
""")
let template = try MustacheTemplate(
string: """
*{{name}}
*{{age}}
*{{company}}
*{{{company}}}
"""
)
let object: [String: Any] = ["name": "Chris", "company": "<b>GitHub</b>"]
XCTAssertEqual(template.render(object), """
*Chris
*
*&lt;b&gt;GitHub&lt;/b&gt;
*<b>GitHub</b>
""")
XCTAssertEqual(
template.render(object),
"""
*Chris
*
*&lt;b&gt;GitHub&lt;/b&gt;
*<b>GitHub</b>
"""
)
}
/// test dotted names
func test_MustacheManualDottedNames() throws {
let template = try MustacheTemplate(string: """
* {{client.name}}
* {{age}}
* {{client.company.name}}
* {{{company.name}}}
""")
let template = try MustacheTemplate(
string: """
* {{client.name}}
* {{age}}
* {{client.company.name}}
* {{{company.name}}}
"""
)
let object: [String: Any] = [
"client": (
name: "Chris & Friends",
age: 50
),
"company": [
"name": "<b>GitHub</b>",
"name": "<b>GitHub</b>"
],
]
XCTAssertEqual(template.render(object), """
* Chris &amp; Friends
*
*
* <b>GitHub</b>
""")
XCTAssertEqual(
template.render(object),
"""
* Chris &amp; Friends
*
*
* <b>GitHub</b>
"""
)
}
/// test implicit operator
func testMustacheManualImplicitOperator() throws {
let template = try MustacheTemplate(string: """
* {{.}}
""")
let template = try MustacheTemplate(
string: """
* {{.}}
"""
)
let object = "Hello!"
XCTAssertEqual(template.render(object), """
* Hello!
""")
XCTAssertEqual(
template.render(object),
"""
* Hello!
"""
)
}
/// test lambda
func test_MustacheManualLambda() throws {
let template = try MustacheTemplate(string: """
* {{time.hour}}
* {{today}}
""")
let template = try MustacheTemplate(
string: """
* {{time.hour}}
* {{today}}
"""
)
let object: [String: Any] = [
"year": 1970,
"month": 1,
@@ -231,113 +253,151 @@ final class TemplateRendererTests: XCTestCase {
return "{{year}}-{{month}}-{{day}}"
},
]
XCTAssertEqual(template.render(object), """
* 0
* 1970-1-1
""")
XCTAssertEqual(
template.render(object),
"""
* 0
* 1970-1-1
"""
)
}
/// test boolean
func testMustacheManualSectionFalse() throws {
let template = try MustacheTemplate(string: """
Shown.
{{#person}}
Never shown!
{{/person}}
""")
let template = try MustacheTemplate(
string: """
Shown.
{{#person}}
Never shown!
{{/person}}
"""
)
let object: [String: Any] = ["person": false]
XCTAssertEqual(template.render(object), """
Shown.
XCTAssertEqual(
template.render(object),
"""
Shown.
""")
"""
)
}
/// test non-empty lists
func testMustacheManualSectionList() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
""")
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """
<b>resque</b>
<b>hub</b>
<b>rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>resque</b>
<b>hub</b>
<b>rip</b>
""")
"""
)
}
/// test non-empty lists
func testMustacheManualSectionList2() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{.}}</b>
{{/repo}}
""")
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{.}}</b>
{{/repo}}
"""
)
let object: [String: Any] = ["repo": ["resque", "hub", "rip"]]
XCTAssertEqual(template.render(object), """
<b>resque</b>
<b>hub</b>
<b>rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>resque</b>
<b>hub</b>
<b>rip</b>
""")
"""
)
}
/// test lambdas
func testMustacheManualSectionLambda() throws {
let template = try MustacheTemplate(string: """
{{#wrapped}}{{name}} is awesome.{{/wrapped}}
""")
let template = try MustacheTemplate(
string: """
{{#wrapped}}{{name}} is awesome.{{/wrapped}}
"""
)
func wrapped(_ s: String) -> Any? {
return "<b>\(s)</b>"
"<b>\(s)</b>"
}
let object: [String: Any] = ["name": "Willy", "wrapped": MustacheLambda(wrapped)]
XCTAssertEqual(template.render(object), """
<b>Willy is awesome.</b>
""")
XCTAssertEqual(
template.render(object),
"""
<b>Willy is awesome.</b>
"""
)
}
/// test setting context object
func testMustacheManualContextObject() throws {
let template = try MustacheTemplate(string: """
{{#person?}}
Hi {{name}}!
{{/person?}}
""")
let template = try MustacheTemplate(
string: """
{{#person?}}
Hi {{name}}!
{{/person?}}
"""
)
let object: [String: Any] = ["person?": ["name": "Jon"]]
XCTAssertEqual(template.render(object), """
Hi Jon!
XCTAssertEqual(
template.render(object),
"""
Hi Jon!
""")
"""
)
}
/// test inverted sections
func testMustacheManualInvertedSection() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
{{^repo}}
No repos :(
{{/repo}}
""")
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
{{^repo}}
No repos :(
{{/repo}}
"""
)
let object: [String: Any] = ["repo": []]
XCTAssertEqual(template.render(object), """
No repos :(
XCTAssertEqual(
template.render(object),
"""
No repos :(
""")
"""
)
}
/// test comments
func testMustacheManualComment() throws {
let template = try MustacheTemplate(string: """
<h1>Today{{! ignore me }}.</h1>
""")
let template = try MustacheTemplate(
string: """
<h1>Today{{! ignore me }}.</h1>
"""
)
let object: [String: Any] = ["repo": []]
XCTAssertEqual(template.render(object), """
<h1>Today.</h1>
""")
XCTAssertEqual(
template.render(object),
"""
<h1>Today.</h1>
"""
)
}
/// test dynamic names
@@ -357,18 +417,23 @@ final class TemplateRendererTests: XCTestCase {
/// test block with defaults
func testMustacheManualBlocksWithDefaults() throws {
let template = try MustacheTemplate(string: """
<h1>{{$title}}The News of Today{{/title}}</h1>
{{$body}}
<p>Nothing special happened.</p>
{{/body}}
let template = try MustacheTemplate(
string: """
<h1>{{$title}}The News of Today{{/title}}</h1>
{{$body}}
<p>Nothing special happened.</p>
{{/body}}
""")
XCTAssertEqual(template.render([]), """
<h1>The News of Today</h1>
<p>Nothing special happened.</p>
"""
)
XCTAssertEqual(
template.render([]),
"""
<h1>The News of Today</h1>
<p>Nothing special happened.</p>
""")
"""
)
}
func testMustacheManualParents() throws {
@@ -405,7 +470,7 @@ final class TemplateRendererTests: XCTestCase {
"headlines": [
"A pug's handler grew mustaches.",
"What an exciting day!",
],
]
]
XCTAssertEqual(
library.render(object, withTemplate: "main"),
@@ -474,11 +539,13 @@ final class TemplateRendererTests: XCTestCase {
}
func testPerformance() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
""")
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
let date = Date()
for _ in 1...10000 {

View File

@@ -17,177 +17,233 @@ import XCTest
final class TransformTests: XCTestCase {
func testLowercased() throws {
let template = try MustacheTemplate(string: """
{{ lowercased(name) }}
""")
let template = try MustacheTemplate(
string: """
{{ lowercased(name) }}
"""
)
let object: [String: Any] = ["name": "Test"]
XCTAssertEqual(template.render(object), "test")
}
func testUppercased() throws {
let template = try MustacheTemplate(string: """
{{ uppercased(name) }}
""")
let template = try MustacheTemplate(
string: """
{{ uppercased(name) }}
"""
)
let object: [String: Any] = ["name": "Test"]
XCTAssertEqual(template.render(object), "TEST")
}
func testNewline() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{name}}</b>
{{/repo}}
""")
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """
<b>resque</b>
<b>hub</b>
<b>rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>resque</b>
<b>hub</b>
<b>rip</b>
""")
"""
)
}
func testFirstLast() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{#first()}}first: {{/first()}}{{#last()}}last: {{/last()}}{{ name }}</b>
{{/repo}}
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{#first()}}first: {{/first()}}{{#last()}}last: {{/last()}}{{ name }}</b>
{{/repo}}
""")
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """
<b>first: resque</b>
<b>hub</b>
<b>last: rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>first: resque</b>
<b>hub</b>
<b>last: rip</b>
""")
"""
)
}
func testIndex() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{#index()}}{{plusone(.)}}{{/index()}}) {{ name }}</b>
{{/repo}}
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{#index()}}{{plusone(.)}}{{/index()}}) {{ name }}</b>
{{/repo}}
""")
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """
<b>1) resque</b>
<b>2) hub</b>
<b>3) rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>1) resque</b>
<b>2) hub</b>
<b>3) rip</b>
""")
"""
)
}
func testDoubleSequenceTransformWorks() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
{{count(reversed(numbers))}}
{{/repo}}
let template = try MustacheTemplate(
string: """
{{#repo}}
{{count(reversed(numbers))}}
{{/repo}}
""")
"""
)
let object: [String: Any] = ["repo": ["numbers": [1, 2, 3]]]
XCTAssertEqual(template.render(object), """
3
XCTAssertEqual(
template.render(object),
"""
3
""")
"""
)
}
func testMultipleTransformWorks() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
{{minusone(plusone(last(reversed(numbers))))}}
{{/repo}}
let template = try MustacheTemplate(
string: """
{{#repo}}
{{minusone(plusone(last(reversed(numbers))))}}
{{/repo}}
""")
"""
)
let object: [String: Any] = ["repo": ["numbers": [5, 4, 3]]]
XCTAssertEqual(template.render(object), """
5
XCTAssertEqual(
template.render(object),
"""
5
""")
"""
)
}
func testNestedTransformWorks() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
{{#uppercased(string)}}{{reversed(.)}}{{/uppercased(string)}}
{{/repo}}
let template = try MustacheTemplate(
string: """
{{#repo}}
{{#uppercased(string)}}{{reversed(.)}}{{/uppercased(string)}}
{{/repo}}
""")
"""
)
let object: [String: Any] = ["repo": ["string": "a123a"]]
XCTAssertEqual(template.render(object), """
A321A
XCTAssertEqual(
template.render(object),
"""
A321A
""")
"""
)
}
func testEvenOdd() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{index()}}) {{#even()}}even {{/even()}}{{#odd()}}odd {{/odd()}}{{ name }}</b>
{{/repo}}
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{index()}}) {{#even()}}even {{/even()}}{{#odd()}}odd {{/odd()}}{{ name }}</b>
{{/repo}}
""")
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """
<b>0) even resque</b>
<b>1) odd hub</b>
<b>2) even rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>0) even resque</b>
<b>1) odd hub</b>
<b>2) even rip</b>
""")
"""
)
}
func testReversed() throws {
let template = try MustacheTemplate(string: """
{{#reversed(repo)}}
<b>{{ name }}</b>
{{/reversed(repo)}}
let template = try MustacheTemplate(
string: """
{{#reversed(repo)}}
<b>{{ name }}</b>
{{/reversed(repo)}}
""")
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """
<b>rip</b>
<b>hub</b>
<b>resque</b>
XCTAssertEqual(
template.render(object),
"""
<b>rip</b>
<b>hub</b>
<b>resque</b>
""")
"""
)
}
func testArrayIndex() throws {
let template = try MustacheTemplate(string: """
{{#repo}}
<b>{{ index() }}) {{ name }}</b>
{{/repo}}
""")
let template = try MustacheTemplate(
string: """
{{#repo}}
<b>{{ index() }}) {{ name }}</b>
{{/repo}}
"""
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """
<b>0) resque</b>
<b>1) hub</b>
<b>2) rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>0) resque</b>
<b>1) hub</b>
<b>2) rip</b>
""")
"""
)
}
func testArraySorted() throws {
let template = try MustacheTemplate(string: """
{{#sorted(repo)}}
<b>{{ index() }}) {{ . }}</b>
{{/sorted(repo)}}
""")
let template = try MustacheTemplate(
string: """
{{#sorted(repo)}}
<b>{{ index() }}) {{ . }}</b>
{{/sorted(repo)}}
"""
)
let object: [String: Any] = ["repo": ["resque", "hub", "rip"]]
XCTAssertEqual(template.render(object), """
<b>0) hub</b>
<b>1) resque</b>
<b>2) rip</b>
XCTAssertEqual(
template.render(object),
"""
<b>0) hub</b>
<b>1) resque</b>
<b>2) rip</b>
""")
"""
)
}
func testDictionaryEmpty() throws {
let template = try MustacheTemplate(string: """
{{#empty(array)}}Array{{/empty(array)}}{{#empty(dictionary)}}Dictionary{{/empty(dictionary)}}
""")
let template = try MustacheTemplate(
string: """
{{#empty(array)}}Array{{/empty(array)}}{{#empty(dictionary)}}Dictionary{{/empty(dictionary)}}
"""
)
let object: [String: Any] = ["array": [], "dictionary": [:]]
XCTAssertEqual(template.render(object), "ArrayDictionary")
}
@@ -199,18 +255,22 @@ final class TransformTests: XCTestCase {
}
func testDictionaryEnumerated() throws {
let template = try MustacheTemplate(string: """
{{#enumerated(.)}}<b>{{ key }} = {{ value }}</b>{{/enumerated(.)}}
""")
let template = try MustacheTemplate(
string: """
{{#enumerated(.)}}<b>{{ key }} = {{ value }}</b>{{/enumerated(.)}}
"""
)
let object: [String: Any] = ["one": 1, "two": 2]
let result = template.render(object)
XCTAssertTrue(result == "<b>one = 1</b><b>two = 2</b>" || result == "<b>two = 2</b><b>one = 1</b>")
}
func testDictionarySortedByKey() throws {
let template = try MustacheTemplate(string: """
{{#sorted(.)}}<b>{{ key }} = {{ value }}</b>{{/sorted(.)}}
""")
let template = try MustacheTemplate(
string: """
{{#sorted(.)}}<b>{{ key }} = {{ value }}</b>{{/sorted(.)}}
"""
)
let object: [String: Any] = ["one": 1, "two": 2, "three": 3]
let result = template.render(object)
XCTAssertEqual(result, "<b>one = 1</b><b>three = 3</b><b>two = 2</b>")

View File

@@ -31,8 +31,6 @@ SWIFT_FORMAT_VERSION=0.53.10
set -eu
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
which swiftformat > /dev/null 2>&1 || (echo "swiftformat not installed. You can install it using 'brew install swiftformat'" ; exit -1)
function replace_acceptable_years() {
# this needs to replace all acceptable forms with 'YEARS'
sed -e 's/20[12][0-9]-20[12][0-9]/YEARS/' -e 's/20[12][0-9]/YEARS/' -e '/^#!/ d'
@@ -40,13 +38,9 @@ function replace_acceptable_years() {
printf "=> Checking format... "
FIRST_OUT="$(git status --porcelain)"
if [[ -n "${CI-""}" ]]; then
printf "(using v$(mint run NickLockwood/SwiftFormat@"$SWIFT_FORMAT_VERSION" --version)) "
mint run NickLockwood/SwiftFormat@"$SWIFT_FORMAT_VERSION" . > /dev/null 2>&1
else
printf "(using v$(swiftformat --version)) "
swiftformat . > /dev/null 2>&1
fi
git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place
git diff --exit-code '*.swift'
SECOND_OUT="$(git status --porcelain)"
if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then
printf "\033[0;31mformatting issues!\033[0m\n"
@@ -55,10 +49,11 @@ if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then
else
printf "\033[0;32mokay.\033[0m\n"
fi
exit
printf "=> Checking license headers... "
tmp=$(mktemp /tmp/.soto-core-sanity_XXXXXX)
exit 0
for language in swift-or-c; do
declare -a matching_files
declare -a exceptions
@@ -66,18 +61,18 @@ for language in swift-or-c; do
matching_files=( -name '*' )
case "$language" in
swift-or-c)
exceptions=( -path '*Sources/INIParser/*' -o -path '*Sources/CSotoExpat/*' -o -path '*Benchmark/.build/*' -o -name Package.swift)
exceptions=( -path '*/Benchmarks/.build/*' -o -name Package.swift)
matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' )
cat > "$tmp" <<"EOF"
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird open source project
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) YEARS the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Hummingbird authors
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
@@ -89,13 +84,13 @@ EOF
cat > "$tmp" <<"EOF"
##===----------------------------------------------------------------------===##
##
## This source file is part of the Hummingbird open source project
## This source file is part of the Hummingbird server framework project
##
## Copyright (c) YEARS the Hummingbird authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of Hummingbird authors
## See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
##
## SPDX-License-Identifier: Apache-2.0
##