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: jobs:
validate: validate:
runs-on: macOS-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Install Dependencies
run: |
brew install mint
mint install NickLockwood/SwiftFormat@0.53.10 --no-link
- name: run script - name: run script
run: ./scripts/validate.sh 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 ### 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", name: "swift-mustache",
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)], platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)],
products: [ products: [
.library(name: "Mustache", targets: ["Mustache"]), .library(name: "Mustache", targets: ["Mustache"])
], ],
dependencies: [], dependencies: [],
targets: [ targets: [

View File

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

View File

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

View File

@@ -50,6 +50,6 @@ public struct MustacheLambda {
} }
internal func callAsFunction(_ s: String) -> Any? { 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 /// Extend dictionary where the key is a string so that it uses the key values to access
/// it values /// it values
extension Dictionary: MustacheParent where Key == String { 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 self.position = string.startIndex
} }
var buffer: String { return self._storage.buffer } var buffer: String { self._storage.buffer }
private(set) var position: String.Index private(set) var position: String.Index
} }
@@ -59,7 +59,10 @@ extension Parser {
/// - Returns: If current character was the one we expected /// - Returns: If current character was the one we expected
mutating func read(_ char: Character) throws -> Bool { mutating func read(_ char: Character) throws -> Bool {
let c = try character() let c = try character()
guard c == char else { unsafeRetreat(); return false } guard c == char else {
unsafeRetreat()
return false
}
return true return true
} }
@@ -84,7 +87,10 @@ extension Parser {
/// - Returns: If current character is in character set /// - Returns: If current character is in character set
mutating func read(_ characterSet: Set<Character>) throws -> Bool { mutating func read(_ characterSet: Set<Character>) throws -> Bool {
let c = try character() let c = try character()
guard characterSet.contains(c) else { unsafeRetreat(); return false } guard characterSet.contains(c) else {
unsafeRetreat()
return false
}
return true return true
} }
@@ -286,13 +292,13 @@ extension Parser {
/// Return whether we have reached the end of the buffer /// Return whether we have reached the end of the buffer
/// - Returns: Have we reached the end /// - Returns: Have we reached the end
func reachedEnd() -> Bool { func reachedEnd() -> Bool {
return self.position == self.buffer.endIndex self.position == self.buffer.endIndex
} }
/// Return whether we are at the start of the buffer /// Return whether we are at the start of the buffer
/// - Returns: Are we are the start /// - Returns: Are we are the start
func atStart() -> Bool { func atStart() -> Bool {
return self.position == self.buffer.startIndex self.position == self.buffer.startIndex
} }
} }
@@ -378,7 +384,7 @@ extension Parser {
// unsafe versions without checks // unsafe versions without checks
extension Parser { extension Parser {
func unsafeCurrent() -> Character { func unsafeCurrent() -> Character {
return self.buffer[self.position] self.buffer[self.position]
} }
mutating func unsafeAdvance() { mutating func unsafeAdvance() {

View File

@@ -327,7 +327,8 @@ extension MustacheTemplate {
} }
if self.isStandalone(&parser, state: state) { if self.isStandalone(&parser, state: state) {
setNewLine = true setNewLine = true
} else if whiteSpaceBefore.count > 0 {} } else if whiteSpaceBefore.count > 0 {
}
let sectionTemplate = try parse(&parser, state: state.withSectionName(name, newLine: setNewLine)) let sectionTemplate = try parse(&parser, state: state.withSectionName(name, newLine: setNewLine))
tokens.append(.blockExpansion(name: name, default: sectionTemplate, indentation: String(whiteSpaceBefore))) tokens.append(.blockExpansion(name: name, default: sectionTemplate, indentation: String(whiteSpaceBefore)))
whiteSpaceBefore = "" whiteSpaceBefore = ""
@@ -512,7 +513,7 @@ extension MustacheTemplate {
} }
static func isStandalone(_ parser: inout Parser, state: ParserState) -> Bool { 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.-_?*") private static let sectionNameCharsWithoutBrackets = Set<Character>("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_?*")

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,7 +50,8 @@ extension AnyDecodable {
self.init(dictionary.mapValues { $0.value }) self.init(dictionary.mapValues { $0.value })
} else { } else {
throw DecodingError.dataCorruptedError( 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") \(result ?? "nil")
expected: expected:
\(test.expected) \(test.expected)
""") """
)
} }
} }
} }
@@ -113,7 +115,8 @@ final class MustacheSpecTests: XCTestCase {
func testSpec(name: String, ignoring: [String] = []) async throws { func testSpec(name: String, ignoring: [String] = []) async throws {
let url = URL( 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) try await testSpec(url: url, ignoring: ignoring)
} }
@@ -135,7 +138,8 @@ final class MustacheSpecTests: XCTestCase {
func testSpec(name: String, only: [String]) async throws { func testSpec(name: String, only: [String]) async throws {
let url = URL( 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) try await testSpec(url: url, only: only)
} }
@@ -161,7 +165,12 @@ final class MustacheSpecTests: XCTestCase {
"Interpolation": MustacheLambda { "world" }, "Interpolation": MustacheLambda { "world" },
"Interpolation - Expansion": MustacheLambda { "{{planet}}" }, "Interpolation - Expansion": MustacheLambda { "{{planet}}" },
"Interpolation - Alternate Delimiters": MustacheLambda { "|planet| => {{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 { ">" }, "Escaping": MustacheLambda { ">" },
"Section": MustacheLambda { text in text == "{{x}}" ? "yes" : "no" }, "Section": MustacheLambda { text in text == "{{x}}" ? "yes" : "no" },
"Section - Expansion": MustacheLambda { text in text + "{{planet}}" + text }, "Section - Expansion": MustacheLambda { text in text + "{{planet}}" + text },

View File

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

View File

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

View File

@@ -17,177 +17,233 @@ import XCTest
final class TransformTests: XCTestCase { final class TransformTests: XCTestCase {
func testLowercased() throws { func testLowercased() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{ lowercased(name) }} {{ lowercased(name) }}
""") """
)
let object: [String: Any] = ["name": "Test"] let object: [String: Any] = ["name": "Test"]
XCTAssertEqual(template.render(object), "test") XCTAssertEqual(template.render(object), "test")
} }
func testUppercased() throws { func testUppercased() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{ uppercased(name) }} {{ uppercased(name) }}
""") """
)
let object: [String: Any] = ["name": "Test"] let object: [String: Any] = ["name": "Test"]
XCTAssertEqual(template.render(object), "TEST") XCTAssertEqual(template.render(object), "TEST")
} }
func testNewline() throws { func testNewline() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
<b>{{name}}</b> <b>{{name}}</b>
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
<b>resque</b> <b>resque</b>
<b>hub</b> <b>hub</b>
<b>rip</b> <b>rip</b>
""") """
)
} }
func testFirstLast() throws { func testFirstLast() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
<b>{{#first()}}first: {{/first()}}{{#last()}}last: {{/last()}}{{ name }}</b> <b>{{#first()}}first: {{/first()}}{{#last()}}last: {{/last()}}{{ name }}</b>
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
<b>first: resque</b> <b>first: resque</b>
<b>hub</b> <b>hub</b>
<b>last: rip</b> <b>last: rip</b>
""") """
)
} }
func testIndex() throws { func testIndex() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
<b>{{#index()}}{{plusone(.)}}{{/index()}}) {{ name }}</b> <b>{{#index()}}{{plusone(.)}}{{/index()}}) {{ name }}</b>
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
<b>1) resque</b> <b>1) resque</b>
<b>2) hub</b> <b>2) hub</b>
<b>3) rip</b> <b>3) rip</b>
""") """
)
} }
func testDoubleSequenceTransformWorks() throws { func testDoubleSequenceTransformWorks() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
{{count(reversed(numbers))}} {{count(reversed(numbers))}}
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": ["numbers": [1, 2, 3]]] let object: [String: Any] = ["repo": ["numbers": [1, 2, 3]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
3 3
""") """
)
} }
func testMultipleTransformWorks() throws { func testMultipleTransformWorks() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
{{minusone(plusone(last(reversed(numbers))))}} {{minusone(plusone(last(reversed(numbers))))}}
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": ["numbers": [5, 4, 3]]] let object: [String: Any] = ["repo": ["numbers": [5, 4, 3]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
5 5
""") """
)
} }
func testNestedTransformWorks() throws { func testNestedTransformWorks() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
{{#uppercased(string)}}{{reversed(.)}}{{/uppercased(string)}} {{#uppercased(string)}}{{reversed(.)}}{{/uppercased(string)}}
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": ["string": "a123a"]] let object: [String: Any] = ["repo": ["string": "a123a"]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
A321A A321A
""") """
)
} }
func testEvenOdd() throws { func testEvenOdd() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
<b>{{index()}}) {{#even()}}even {{/even()}}{{#odd()}}odd {{/odd()}}{{ name }}</b> <b>{{index()}}) {{#even()}}even {{/even()}}{{#odd()}}odd {{/odd()}}{{ name }}</b>
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
<b>0) even resque</b> <b>0) even resque</b>
<b>1) odd hub</b> <b>1) odd hub</b>
<b>2) even rip</b> <b>2) even rip</b>
""") """
)
} }
func testReversed() throws { func testReversed() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#reversed(repo)}} {{#reversed(repo)}}
<b>{{ name }}</b> <b>{{ name }}</b>
{{/reversed(repo)}} {{/reversed(repo)}}
""") """
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
<b>rip</b> <b>rip</b>
<b>hub</b> <b>hub</b>
<b>resque</b> <b>resque</b>
""") """
)
} }
func testArrayIndex() throws { func testArrayIndex() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#repo}} {{#repo}}
<b>{{ index() }}) {{ name }}</b> <b>{{ index() }}) {{ name }}</b>
{{/repo}} {{/repo}}
""") """
)
let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]] let object: [String: Any] = ["repo": [["name": "resque"], ["name": "hub"], ["name": "rip"]]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
<b>0) resque</b> <b>0) resque</b>
<b>1) hub</b> <b>1) hub</b>
<b>2) rip</b> <b>2) rip</b>
""") """
)
} }
func testArraySorted() throws { func testArraySorted() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#sorted(repo)}} {{#sorted(repo)}}
<b>{{ index() }}) {{ . }}</b> <b>{{ index() }}) {{ . }}</b>
{{/sorted(repo)}} {{/sorted(repo)}}
""") """
)
let object: [String: Any] = ["repo": ["resque", "hub", "rip"]] let object: [String: Any] = ["repo": ["resque", "hub", "rip"]]
XCTAssertEqual(template.render(object), """ XCTAssertEqual(
template.render(object),
"""
<b>0) hub</b> <b>0) hub</b>
<b>1) resque</b> <b>1) resque</b>
<b>2) rip</b> <b>2) rip</b>
""") """
)
} }
func testDictionaryEmpty() throws { func testDictionaryEmpty() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#empty(array)}}Array{{/empty(array)}}{{#empty(dictionary)}}Dictionary{{/empty(dictionary)}} {{#empty(array)}}Array{{/empty(array)}}{{#empty(dictionary)}}Dictionary{{/empty(dictionary)}}
""") """
)
let object: [String: Any] = ["array": [], "dictionary": [:]] let object: [String: Any] = ["array": [], "dictionary": [:]]
XCTAssertEqual(template.render(object), "ArrayDictionary") XCTAssertEqual(template.render(object), "ArrayDictionary")
} }
@@ -199,18 +255,22 @@ final class TransformTests: XCTestCase {
} }
func testDictionaryEnumerated() throws { func testDictionaryEnumerated() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#enumerated(.)}}<b>{{ key }} = {{ value }}</b>{{/enumerated(.)}} {{#enumerated(.)}}<b>{{ key }} = {{ value }}</b>{{/enumerated(.)}}
""") """
)
let object: [String: Any] = ["one": 1, "two": 2] let object: [String: Any] = ["one": 1, "two": 2]
let result = template.render(object) let result = template.render(object)
XCTAssertTrue(result == "<b>one = 1</b><b>two = 2</b>" || result == "<b>two = 2</b><b>one = 1</b>") XCTAssertTrue(result == "<b>one = 1</b><b>two = 2</b>" || result == "<b>two = 2</b><b>one = 1</b>")
} }
func testDictionarySortedByKey() throws { func testDictionarySortedByKey() throws {
let template = try MustacheTemplate(string: """ let template = try MustacheTemplate(
string: """
{{#sorted(.)}}<b>{{ key }} = {{ value }}</b>{{/sorted(.)}} {{#sorted(.)}}<b>{{ key }} = {{ value }}</b>{{/sorted(.)}}
""") """
)
let object: [String: Any] = ["one": 1, "two": 2, "three": 3] let object: [String: Any] = ["one": 1, "two": 2, "three": 3]
let result = template.render(object) let result = template.render(object)
XCTAssertEqual(result, "<b>one = 1</b><b>three = 3</b><b>two = 2</b>") 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 set -eu
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 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() { function replace_acceptable_years() {
# this needs to replace all acceptable forms with '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' 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... " printf "=> Checking format... "
FIRST_OUT="$(git status --porcelain)" FIRST_OUT="$(git status --porcelain)"
if [[ -n "${CI-""}" ]]; then git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place
printf "(using v$(mint run NickLockwood/SwiftFormat@"$SWIFT_FORMAT_VERSION" --version)) " git diff --exit-code '*.swift'
mint run NickLockwood/SwiftFormat@"$SWIFT_FORMAT_VERSION" . > /dev/null 2>&1
else
printf "(using v$(swiftformat --version)) "
swiftformat . > /dev/null 2>&1
fi
SECOND_OUT="$(git status --porcelain)" SECOND_OUT="$(git status --porcelain)"
if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then
printf "\033[0;31mformatting issues!\033[0m\n" printf "\033[0;31mformatting issues!\033[0m\n"
@@ -55,10 +49,11 @@ if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then
else else
printf "\033[0;32mokay.\033[0m\n" printf "\033[0;32mokay.\033[0m\n"
fi fi
exit
printf "=> Checking license headers... " printf "=> Checking license headers... "
tmp=$(mktemp /tmp/.soto-core-sanity_XXXXXX) tmp=$(mktemp /tmp/.soto-core-sanity_XXXXXX)
exit 0
for language in swift-or-c; do for language in swift-or-c; do
declare -a matching_files declare -a matching_files
declare -a exceptions declare -a exceptions
@@ -66,18 +61,18 @@ for language in swift-or-c; do
matching_files=( -name '*' ) matching_files=( -name '*' )
case "$language" in case "$language" in
swift-or-c) 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' ) matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' )
cat > "$tmp" <<"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 // Copyright (c) YEARS the Hummingbird authors
// Licensed under Apache License v2.0 // Licensed under Apache License v2.0
// //
// See LICENSE.txt for license information // 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 // SPDX-License-Identifier: Apache-2.0
// //
@@ -89,13 +84,13 @@ EOF
cat > "$tmp" <<"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 ## Copyright (c) YEARS the Hummingbird authors
## Licensed under Apache License v2.0 ## Licensed under Apache License v2.0
## ##
## See LICENSE.txt for license information ## 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 ## SPDX-License-Identifier: Apache-2.0
## ##