Merge pull request #323 from stencilproject/feature/drop-swift4
Drop Swift 4 support
This commit is contained in:
185
.swiftlint.yml
185
.swiftlint.yml
@@ -1,90 +1,112 @@
|
||||
swiftlint_version: 0.48.0
|
||||
|
||||
disabled_rules:
|
||||
# Remove this once we remove old swift support
|
||||
- implicit_return
|
||||
|
||||
opt_in_rules:
|
||||
- anyobject_protocol
|
||||
- array_init
|
||||
- attributes
|
||||
- closure_body_length
|
||||
- closure_end_indentation
|
||||
- closure_spacing
|
||||
- collection_alignment
|
||||
- contains_over_filter_count
|
||||
- contains_over_filter_is_empty
|
||||
- contains_over_first_not_nil
|
||||
- contains_over_range_nil_comparison
|
||||
- convenience_type
|
||||
- discouraged_optional_boolean
|
||||
- discouraged_optional_collection
|
||||
- duplicate_enum_cases
|
||||
- duplicate_imports
|
||||
- empty_collection_literal
|
||||
- empty_count
|
||||
- empty_string
|
||||
- fallthrough
|
||||
- fatal_error_message
|
||||
- first_where
|
||||
- flatmap_over_map_reduce
|
||||
- force_unwrapping
|
||||
- identical_operands
|
||||
- inert_defer
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
- legacy_hashing
|
||||
- legacy_random
|
||||
- literal_expression_end_indentation
|
||||
- lower_acl_than_parent
|
||||
- modifier_order
|
||||
- multiline_arguments
|
||||
- multiline_function_chains
|
||||
- multiline_literal_brackets
|
||||
- multiline_parameters
|
||||
- multiline_parameters_brackets
|
||||
- nslocalizedstring_key
|
||||
- nsobject_prefer_isequal
|
||||
- number_separator
|
||||
- object_literal
|
||||
- operator_usage_whitespace
|
||||
- overridden_super_call
|
||||
- override_in_extension
|
||||
- prefer_self_type_over_type_of_self
|
||||
- private_action
|
||||
- private_outlet
|
||||
- prohibited_super_call
|
||||
- raw_value_for_camel_cased_codable_enum
|
||||
- reduce_boolean
|
||||
- reduce_into
|
||||
- redundant_nil_coalescing
|
||||
- redundant_objc_attribute
|
||||
- sorted_first_last
|
||||
- sorted_imports
|
||||
- static_operator
|
||||
- strong_iboutlet
|
||||
- toggle_bool
|
||||
- trailing_closure
|
||||
- unavailable_function
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unowned_variable_capture
|
||||
- unused_capture_list
|
||||
- unused_control_flow_label
|
||||
- unused_declaration
|
||||
- unused_setter_value
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_closing_braces
|
||||
- vertical_whitespace_opening_braces
|
||||
- xct_specific_matcher
|
||||
- yoda_condition
|
||||
# Enable this again once we remove old swift support
|
||||
# - optional_enum_case_matching
|
||||
# - legacy_multiple
|
||||
- accessibility_label_for_image
|
||||
- anonymous_argument_in_multiline_closure
|
||||
- anyobject_protocol
|
||||
- array_init
|
||||
- attributes
|
||||
- balanced_xctest_lifecycle
|
||||
- closure_body_length
|
||||
- closure_end_indentation
|
||||
- closure_spacing
|
||||
- collection_alignment
|
||||
- comment_spacing
|
||||
- conditional_returns_on_newline
|
||||
- contains_over_filter_count
|
||||
- contains_over_filter_is_empty
|
||||
- contains_over_first_not_nil
|
||||
- contains_over_range_nil_comparison
|
||||
- convenience_type
|
||||
- discarded_notification_center_observer
|
||||
- discouraged_assert
|
||||
- discouraged_none_name
|
||||
- discouraged_optional_boolean
|
||||
- discouraged_optional_collection
|
||||
- empty_collection_literal
|
||||
- empty_count
|
||||
- empty_string
|
||||
- empty_xctest_method
|
||||
- enum_case_associated_values_count
|
||||
- fallthrough
|
||||
- fatal_error_message
|
||||
- file_header
|
||||
- first_where
|
||||
- flatmap_over_map_reduce
|
||||
- force_unwrapping
|
||||
- ibinspectable_in_extension
|
||||
- identical_operands
|
||||
- implicit_return
|
||||
- implicitly_unwrapped_optional
|
||||
- inclusive_language
|
||||
- indentation_width
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
- legacy_multiple
|
||||
- legacy_objc_type
|
||||
- legacy_random
|
||||
- literal_expression_end_indentation
|
||||
- lower_acl_than_parent
|
||||
- missing_docs
|
||||
- modifier_order
|
||||
- multiline_arguments
|
||||
- multiline_arguments_brackets
|
||||
- multiline_function_chains
|
||||
- multiline_literal_brackets
|
||||
- multiline_parameters
|
||||
- multiline_parameters_brackets
|
||||
- nslocalizedstring_key
|
||||
- nslocalizedstring_require_bundle
|
||||
- number_separator
|
||||
- operator_usage_whitespace
|
||||
- optional_enum_case_matching
|
||||
- overridden_super_call
|
||||
- override_in_extension
|
||||
- prefer_self_in_static_references
|
||||
- prefer_self_type_over_type_of_self
|
||||
- prefer_zero_over_explicit_init
|
||||
- prefixed_toplevel_constant
|
||||
- private_action
|
||||
- private_outlet
|
||||
- private_subject
|
||||
- prohibited_super_call
|
||||
- raw_value_for_camel_cased_codable_enum
|
||||
- reduce_into
|
||||
- redundant_nil_coalescing
|
||||
- redundant_type_annotation
|
||||
- required_enum_case
|
||||
- return_value_from_void_function
|
||||
- single_test_class
|
||||
- sorted_first_last
|
||||
- sorted_imports
|
||||
- static_operator
|
||||
- strong_iboutlet
|
||||
- switch_case_on_newline
|
||||
- test_case_accessibility
|
||||
- toggle_bool
|
||||
- trailing_closure
|
||||
- unavailable_function
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unowned_variable_capture
|
||||
- unused_closure_parameter
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_closing_braces
|
||||
- vertical_whitespace_opening_braces
|
||||
- void_function_in_ternary
|
||||
- weak_delegate
|
||||
- xct_specific_matcher
|
||||
- yoda_condition
|
||||
|
||||
# Rules customization
|
||||
closure_body_length:
|
||||
warning: 25
|
||||
|
||||
conditional_returns_on_newline:
|
||||
if_only: true
|
||||
|
||||
indentation_width:
|
||||
indentation_width: 2
|
||||
|
||||
line_length:
|
||||
warning: 120
|
||||
error: 200
|
||||
@@ -92,8 +114,3 @@ line_length:
|
||||
nesting:
|
||||
type_level:
|
||||
warning: 2
|
||||
|
||||
# Exclude generated files
|
||||
excluded:
|
||||
- .build
|
||||
- Tests/StencilTests/XCTestManifests.swift
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
### Breaking
|
||||
|
||||
_None_
|
||||
- Drop support for Swift < 5. For Swift 4.2 support, you should use Stencil 0.14.2.
|
||||
[David Jennes](https://github.com/djbe)
|
||||
[#323](https://github.com/stencilproject/Stencil/pull/323)
|
||||
|
||||
### Enhancements
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version:4.2
|
||||
// swift-tools-version:5.0
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
@@ -13,11 +13,11 @@ let package = Package(
|
||||
targets: [
|
||||
.target(name: "Stencil", dependencies: [
|
||||
"PathKit"
|
||||
], path: "Sources"),
|
||||
]),
|
||||
.testTarget(name: "StencilTests", dependencies: [
|
||||
"Stencil",
|
||||
"Spectre"
|
||||
])
|
||||
],
|
||||
swiftLanguageVersions: [.v4_2]
|
||||
swiftLanguageVersions: [.v5]
|
||||
)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// swift-tools-version:5.0
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Stencil",
|
||||
products: [
|
||||
.library(name: "Stencil", targets: ["Stencil"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/kylef/PathKit.git", from: "1.0.1"),
|
||||
.package(url: "https://github.com/kylef/Spectre.git", from: "0.10.1")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Stencil", dependencies: [
|
||||
"PathKit"
|
||||
], path: "Sources"),
|
||||
.testTarget(name: "StencilTests", dependencies: [
|
||||
"Stencil",
|
||||
"Spectre"
|
||||
])
|
||||
],
|
||||
swiftLanguageVersions: [.v4_2, .v5]
|
||||
)
|
||||
@@ -1,7 +1,5 @@
|
||||
# Stencil
|
||||
|
||||
[](https://travis-ci.org/stencilproject/Stencil)
|
||||
|
||||
Stencil is a simple and powerful template language for Swift. It provides a
|
||||
syntax similar to Django and Mustache. If you're familiar with these, you will
|
||||
feel right at home with Stencil.
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
public class Context {
|
||||
var dictionaries: [[String: Any?]]
|
||||
|
||||
/// The context's environment, such as registered extensions, classes, …
|
||||
public let environment: Environment
|
||||
|
||||
/// Create a context from a dictionary (and an env.)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - dictionary: The context's data
|
||||
/// - environment: Environment such as extensions, …
|
||||
public init(dictionary: [String: Any] = [:], environment: Environment? = nil) {
|
||||
if !dictionary.isEmpty {
|
||||
dictionaries = [dictionary]
|
||||
@@ -14,6 +20,7 @@ public class Context {
|
||||
self.environment = environment ?? Environment()
|
||||
}
|
||||
|
||||
/// Access variables in this context by name
|
||||
public subscript(key: String) -> Any? {
|
||||
/// Retrieves a variable's value, starting at the current context and going upwards
|
||||
get {
|
||||
@@ -36,22 +43,35 @@ public class Context {
|
||||
}
|
||||
|
||||
/// Push a new level into the Context
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - dictionary: The new level data
|
||||
fileprivate func push(_ dictionary: [String: Any] = [:]) {
|
||||
dictionaries.append(dictionary)
|
||||
}
|
||||
|
||||
/// Pop the last level off of the Context
|
||||
///
|
||||
/// - returns: The popped level
|
||||
fileprivate func pop() -> [String: Any?]? {
|
||||
return dictionaries.popLast()
|
||||
dictionaries.popLast()
|
||||
}
|
||||
|
||||
/// Push a new level onto the context for the duration of the execution of the given closure
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - dictionary: The new level data
|
||||
/// - closure: The closure to execute
|
||||
/// - returns: Return value of the closure
|
||||
public func push<Result>(dictionary: [String: Any] = [:], closure: (() throws -> Result)) rethrows -> Result {
|
||||
push(dictionary)
|
||||
defer { _ = pop() }
|
||||
return try closure()
|
||||
}
|
||||
|
||||
/// Flatten all levels of context data into 1, merging duplicate variables
|
||||
///
|
||||
/// - returns: All collected variables
|
||||
public func flatten() -> [String: Any] {
|
||||
var accumulator: [String: Any] = [:]
|
||||
|
||||
@@ -6,10 +6,13 @@ public protocol DynamicMemberLookup {
|
||||
}
|
||||
|
||||
public extension DynamicMemberLookup where Self: RawRepresentable {
|
||||
/// Get a value for a given `String` key
|
||||
subscript(dynamicMember member: String) -> Any? {
|
||||
switch member {
|
||||
case "rawValue": return rawValue
|
||||
default: return nil
|
||||
case "rawValue":
|
||||
return rawValue
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,18 @@
|
||||
/// Container for environment data, such as registered extensions
|
||||
public struct Environment {
|
||||
/// The class for loading new templates
|
||||
public let templateClass: Template.Type
|
||||
/// List of registered extensions
|
||||
public var extensions: [Extension]
|
||||
/// Mechanism for loading new files
|
||||
public var loader: Loader?
|
||||
|
||||
/// Basic initializer
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - loader: Mechanism for loading new files
|
||||
/// - extensions: List of extension containers
|
||||
/// - templateClass: Class for newly loaded templates
|
||||
public init(
|
||||
loader: Loader? = nil,
|
||||
extensions: [Extension] = [],
|
||||
@@ -13,6 +23,11 @@ public struct Environment {
|
||||
self.extensions = extensions + [DefaultExtension()]
|
||||
}
|
||||
|
||||
/// Load a template with the given name
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: Name of the template
|
||||
/// - returns: Loaded template instance
|
||||
public func loadTemplate(name: String) throws -> Template {
|
||||
if let loader = loader {
|
||||
return try loader.loadTemplate(name: name, environment: self)
|
||||
@@ -21,6 +36,11 @@ public struct Environment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a template with the given names
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - names: Names of the template
|
||||
/// - returns: Loaded template instance
|
||||
public func loadTemplate(names: [String]) throws -> Template {
|
||||
if let loader = loader {
|
||||
return try loader.loadTemplate(names: names, environment: self)
|
||||
@@ -29,11 +49,23 @@ public struct Environment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a template with the given name, providing some data
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: Name of the template
|
||||
/// - context: Data for rendering
|
||||
/// - returns: Rendered output
|
||||
public func renderTemplate(name: String, context: [String: Any] = [:]) throws -> String {
|
||||
let template = try loadTemplate(name: name)
|
||||
return try render(template: template, context: context)
|
||||
}
|
||||
|
||||
/// Render the given template string, providing some data
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - string: Template string
|
||||
/// - context: Data for rendering
|
||||
/// - returns: Rendered output
|
||||
public func renderTemplate(string: String, context: [String: Any] = [:]) throws -> String {
|
||||
let template = templateClass.init(templateString: string, environment: self)
|
||||
return try render(template: template, context: context)
|
||||
@@ -20,12 +20,12 @@ public class TemplateDoesNotExist: Error, CustomStringConvertible {
|
||||
|
||||
public struct TemplateSyntaxError: Error, Equatable, CustomStringConvertible {
|
||||
public let reason: String
|
||||
public var description: String { return reason }
|
||||
public var description: String { reason }
|
||||
public internal(set) var token: Token?
|
||||
public internal(set) var stackTrace: [Token]
|
||||
public var templateName: String? { return token?.sourceMap.filename }
|
||||
public var templateName: String? { token?.sourceMap.filename }
|
||||
var allTokens: [Token] {
|
||||
return stackTrace + (token.map { [$0] } ?? [])
|
||||
stackTrace + (token.map { [$0] } ?? [])
|
||||
}
|
||||
|
||||
public init(reason: String, token: Token? = nil, stackTrace: [Token] = []) {
|
||||
@@ -18,11 +18,11 @@ final class StaticExpression: Expression, CustomStringConvertible {
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
return value
|
||||
value
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "\(value)"
|
||||
"\(value)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ final class VariableExpression: Expression, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "(variable: \(variable))"
|
||||
"(variable: \(variable))"
|
||||
}
|
||||
|
||||
/// Resolves a variable in the given context as boolean
|
||||
@@ -60,7 +60,7 @@ final class VariableExpression: Expression, CustomStringConvertible {
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
return try resolve(context: context, variable: variable)
|
||||
try resolve(context: context, variable: variable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +72,11 @@ final class NotExpression: Expression, PrefixOperator, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "not \(expression)"
|
||||
"not \(expression)"
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
return try !expression.evaluate(context: context)
|
||||
try !expression.evaluate(context: context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ final class InExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "(\(lhs) in \(rhs))"
|
||||
"(\(lhs) in \(rhs))"
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
@@ -125,7 +125,7 @@ final class OrExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "(\(lhs) or \(rhs))"
|
||||
"(\(lhs) or \(rhs))"
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
@@ -148,7 +148,7 @@ final class AndExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "(\(lhs) and \(rhs))"
|
||||
"(\(lhs) and \(rhs))"
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
@@ -171,7 +171,7 @@ class EqualityExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "(\(lhs) == \(rhs))"
|
||||
"(\(lhs) == \(rhs))"
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
@@ -206,7 +206,7 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "(\(lhs) \(symbol) \(rhs))"
|
||||
"(\(lhs) \(symbol) \(rhs))"
|
||||
}
|
||||
|
||||
func evaluate(context: Context) throws -> Bool {
|
||||
@@ -225,97 +225,97 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||
}
|
||||
|
||||
var symbol: String {
|
||||
return ""
|
||||
""
|
||||
}
|
||||
|
||||
func compare(lhs: Number, rhs: Number) -> Bool {
|
||||
return false
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
class MoreThanExpression: NumericExpression {
|
||||
override var symbol: String {
|
||||
return ">"
|
||||
">"
|
||||
}
|
||||
|
||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||
return lhs > rhs
|
||||
lhs > rhs
|
||||
}
|
||||
}
|
||||
|
||||
class MoreThanEqualExpression: NumericExpression {
|
||||
override var symbol: String {
|
||||
return ">="
|
||||
">="
|
||||
}
|
||||
|
||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||
return lhs >= rhs
|
||||
lhs >= rhs
|
||||
}
|
||||
}
|
||||
|
||||
class LessThanExpression: NumericExpression {
|
||||
override var symbol: String {
|
||||
return "<"
|
||||
"<"
|
||||
}
|
||||
|
||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||
return lhs < rhs
|
||||
lhs < rhs
|
||||
}
|
||||
}
|
||||
|
||||
class LessThanEqualExpression: NumericExpression {
|
||||
override var symbol: String {
|
||||
return "<="
|
||||
"<="
|
||||
}
|
||||
|
||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||
return lhs <= rhs
|
||||
lhs <= rhs
|
||||
}
|
||||
}
|
||||
|
||||
class InequalityExpression: EqualityExpression {
|
||||
override var description: String {
|
||||
return "(\(lhs) != \(rhs))"
|
||||
"(\(lhs) != \(rhs))"
|
||||
}
|
||||
|
||||
override func evaluate(context: Context) throws -> Bool {
|
||||
return try !super.evaluate(context: context)
|
||||
try !super.evaluate(context: context)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
func toNumber(value: Any) -> Number? {
|
||||
if let value = value as? Float {
|
||||
return Number(value)
|
||||
} else if let value = value as? Double {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int8 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int16 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int32 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int64 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt8 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt16 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt32 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt64 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Number {
|
||||
return value
|
||||
} else if let value = value as? Float64 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Float32 {
|
||||
return Number(value)
|
||||
}
|
||||
if let value = value as? Float {
|
||||
return Number(value)
|
||||
} else if let value = value as? Double {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int8 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int16 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int32 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Int64 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt8 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt16 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt32 {
|
||||
return Number(value)
|
||||
} else if let value = value as? UInt64 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Number {
|
||||
return value
|
||||
} else if let value = value as? Float64 {
|
||||
return Number(value)
|
||||
} else if let value = value as? Float32 {
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
/// Container for registered tags and filters
|
||||
open class Extension {
|
||||
typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||
var tags = [String: TagParser]()
|
||||
|
||||
var tags = [String: TagParser]()
|
||||
var filters = [String: Filter]()
|
||||
|
||||
/// Simple initializer
|
||||
public init() {
|
||||
}
|
||||
|
||||
@@ -20,11 +22,11 @@ open class Extension {
|
||||
}
|
||||
|
||||
/// Registers boolean filter with it's negative counterpart
|
||||
// swiftlint:disable:next discouraged_optional_boolean
|
||||
public func registerFilter(name: String, negativeFilterName: String, filter: @escaping (Any?) throws -> Bool?) {
|
||||
// swiftlint:disable:previous discouraged_optional_boolean
|
||||
filters[name] = .simple(filter)
|
||||
filters[negativeFilterName] = .simple {
|
||||
guard let result = try filter($0) else { return nil }
|
||||
filters[negativeFilterName] = .simple { value in
|
||||
guard let result = try filter(value) else { return nil }
|
||||
return !result
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,11 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
||||
var indentWidth = 4
|
||||
if !arguments.isEmpty {
|
||||
guard let value = arguments[0] as? Int else {
|
||||
throw TemplateSyntaxError("""
|
||||
throw TemplateSyntaxError(
|
||||
"""
|
||||
'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))
|
||||
""")
|
||||
"""
|
||||
)
|
||||
}
|
||||
indentWidth = value
|
||||
}
|
||||
@@ -84,9 +86,11 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
||||
var indentationChar = " "
|
||||
if arguments.count > 1 {
|
||||
guard let value = arguments[1] as? String else {
|
||||
throw TemplateSyntaxError("""
|
||||
throw TemplateSyntaxError(
|
||||
"""
|
||||
'indent' filter indentation argument must be a String (\(String(describing: arguments[1]))
|
||||
""")
|
||||
"""
|
||||
)
|
||||
}
|
||||
indentationChar = value
|
||||
}
|
||||
@@ -12,11 +12,11 @@ class ForNode: NodeType {
|
||||
let components = token.components
|
||||
|
||||
func hasToken(_ token: String, at index: Int) -> Bool {
|
||||
return components.count > (index + 1) && components[index] == token
|
||||
components.count > (index + 1) && components[index] == token
|
||||
}
|
||||
|
||||
func endsOrHasToken(_ token: String, at index: Int) -> Bool {
|
||||
return components.count == index || hasToken(token, at: index)
|
||||
components.count == index || hasToken(token, at: index)
|
||||
}
|
||||
|
||||
guard hasToken("in", at: 2) && endsOrHasToken("where", at: 4) else {
|
||||
@@ -154,9 +154,9 @@ class ForNode: NodeType {
|
||||
} else if let resolved = resolved {
|
||||
let mirror = Mirror(reflecting: resolved)
|
||||
switch mirror.displayStyle {
|
||||
case .struct?, .tuple?:
|
||||
case .struct, .tuple:
|
||||
values = Array(mirror.children)
|
||||
case .class?:
|
||||
case .class:
|
||||
var children = Array(mirror.children)
|
||||
var currentMirror: Mirror? = mirror
|
||||
while let superclassMirror = currentMirror?.superclassMirror {
|
||||
@@ -10,23 +10,23 @@ enum Operator {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
static let all: [Operator] = [
|
||||
.infix("in", 5, InExpression.self),
|
||||
.infix("or", 6, OrExpression.self),
|
||||
.infix("and", 7, AndExpression.self),
|
||||
.prefix("not", 8, NotExpression.self),
|
||||
.infix("==", 10, EqualityExpression.self),
|
||||
.infix("!=", 10, InequalityExpression.self),
|
||||
.infix(">", 10, MoreThanExpression.self),
|
||||
.infix(">=", 10, MoreThanEqualExpression.self),
|
||||
.infix("<", 10, LessThanExpression.self),
|
||||
.infix("<=", 10, LessThanEqualExpression.self)
|
||||
]
|
||||
}
|
||||
|
||||
let operators: [Operator] = [
|
||||
.infix("in", 5, InExpression.self),
|
||||
.infix("or", 6, OrExpression.self),
|
||||
.infix("and", 7, AndExpression.self),
|
||||
.prefix("not", 8, NotExpression.self),
|
||||
.infix("==", 10, EqualityExpression.self),
|
||||
.infix("!=", 10, InequalityExpression.self),
|
||||
.infix(">", 10, MoreThanExpression.self),
|
||||
.infix(">=", 10, MoreThanEqualExpression.self),
|
||||
.infix("<", 10, LessThanExpression.self),
|
||||
.infix("<=", 10, LessThanEqualExpression.self)
|
||||
]
|
||||
|
||||
func findOperator(name: String) -> Operator? {
|
||||
for `operator` in operators where `operator`.name == name {
|
||||
for `operator` in Operator.all where `operator`.name == name {
|
||||
return `operator`
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ final class IfExpressionParser {
|
||||
}
|
||||
|
||||
static func parser(components: [String], environment: Environment, token: Token) throws -> IfExpressionParser {
|
||||
return try IfExpressionParser(components: ArraySlice(components), environment: environment, token: token)
|
||||
try IfExpressionParser(components: ArraySlice(components), environment: environment, token: token)
|
||||
}
|
||||
|
||||
private init(components: ArraySlice<String>, environment: Environment, token: Token) throws {
|
||||
@@ -117,7 +117,7 @@ final class IfExpressionParser {
|
||||
|
||||
if component == "(" {
|
||||
bracketsBalance += 1
|
||||
let (expression, parsedCount) = try IfExpressionParser.subExpression(
|
||||
let (expression, parsedCount) = try Self.subExpression(
|
||||
from: components.suffix(from: index + 1),
|
||||
environment: environment,
|
||||
token: token
|
||||
@@ -152,11 +152,11 @@ final class IfExpressionParser {
|
||||
token: Token
|
||||
) throws -> (Expression, Int) {
|
||||
var bracketsBalance = 1
|
||||
let subComponents = components.prefix {
|
||||
if $0 == "(" {
|
||||
bracketsBalance += 1
|
||||
} else if $0 == ")" {
|
||||
bracketsBalance -= 1
|
||||
let subComponents = components.prefix { component in
|
||||
if component == "(" {
|
||||
bracketsBalance += 1
|
||||
} else if component == ")" {
|
||||
bracketsBalance -= 1
|
||||
}
|
||||
return bracketsBalance != 0
|
||||
}
|
||||
@@ -220,7 +220,7 @@ final class IfCondition {
|
||||
}
|
||||
|
||||
func render(_ context: Context) throws -> String {
|
||||
return try context.push {
|
||||
try context.push {
|
||||
try renderNodes(nodes, context)
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,13 @@ class IncludeNode: NodeType {
|
||||
let bits = token.components
|
||||
|
||||
guard bits.count == 2 || bits.count == 3 else {
|
||||
throw TemplateSyntaxError("""
|
||||
throw TemplateSyntaxError(
|
||||
"""
|
||||
'include' tag requires one argument, the template file to be included. \
|
||||
A second optional argument can be used to specify the context that will \
|
||||
be passed to the included file
|
||||
""")
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
return IncludeNode(templateName: Variable(bits[1]), includeContext: bits.count == 3 ? bits[2] : nil, token: token)
|
||||
@@ -1,5 +1,5 @@
|
||||
class BlockContext {
|
||||
class var contextKey: String { return "block_context" }
|
||||
class var contextKey: String { "block_context" }
|
||||
|
||||
// contains mapping of block names to their nodes and templates where they are defined
|
||||
var blocks: [String: [BlockNode]]
|
||||
@@ -23,10 +23,10 @@ struct Lexer {
|
||||
self.templateName = templateName
|
||||
self.templateString = templateString
|
||||
|
||||
self.lines = templateString.components(separatedBy: .newlines).enumerated().compactMap {
|
||||
guard !$0.element.isEmpty,
|
||||
let range = templateString.range(of: $0.element) else { return nil }
|
||||
return (content: $0.element, number: UInt($0.offset + 1), range)
|
||||
self.lines = zip(1..., templateString.components(separatedBy: .newlines)).compactMap { index, line in
|
||||
guard !line.isEmpty,
|
||||
let range = templateString.range(of: line) else { return nil }
|
||||
return (content: line, number: UInt(index), range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,12 +79,12 @@ struct Lexer {
|
||||
|
||||
let scanner = Scanner(templateString)
|
||||
while !scanner.isEmpty {
|
||||
if let (char, text) = scanner.scanForTokenStart(Lexer.tokenChars) {
|
||||
if let (char, text) = scanner.scanForTokenStart(Self.tokenChars) {
|
||||
if !text.isEmpty {
|
||||
tokens.append(createToken(string: text, at: scanner.range))
|
||||
}
|
||||
|
||||
guard let end = Lexer.tokenCharMap[char] else { continue }
|
||||
guard let end = Self.tokenCharMap[char] else { continue }
|
||||
let result = scanner.scanForTokenEnd(end)
|
||||
tokens.append(createToken(string: result, at: scanner.range))
|
||||
} else {
|
||||
@@ -127,7 +127,7 @@ class Scanner {
|
||||
}
|
||||
|
||||
var isEmpty: Bool {
|
||||
return content.isEmpty
|
||||
content.isEmpty
|
||||
}
|
||||
|
||||
/// Scans for the end of a token, with a specific ending character. If we're
|
||||
@@ -144,8 +144,8 @@ class Scanner {
|
||||
func scanForTokenEnd(_ tokenChar: Unicode.Scalar) -> String {
|
||||
var foundChar = false
|
||||
|
||||
for (index, char) in content.unicodeScalars.enumerated() {
|
||||
if foundChar && char == Scanner.tokenEndDelimiter {
|
||||
for (index, char) in zip(0..., content.unicodeScalars) {
|
||||
if foundChar && char == Self.tokenEndDelimiter {
|
||||
let result = String(content.unicodeScalars.prefix(index + 1))
|
||||
content = String(content.unicodeScalars.dropFirst(index + 1))
|
||||
range = range.upperBound..<originalContent.unicodeScalars.index(range.upperBound, offsetBy: index + 1)
|
||||
@@ -178,14 +178,14 @@ class Scanner {
|
||||
var foundBrace = false
|
||||
|
||||
range = range.upperBound..<range.upperBound
|
||||
for (index, char) in content.unicodeScalars.enumerated() {
|
||||
for (index, char) in zip(0..., content.unicodeScalars) {
|
||||
if foundBrace && tokenChars.contains(char) {
|
||||
let result = String(content.unicodeScalars.prefix(index - 1))
|
||||
content = String(content.unicodeScalars.dropFirst(index - 1))
|
||||
range = range.upperBound..<originalContent.unicodeScalars.index(range.upperBound, offsetBy: index - 1)
|
||||
return (char, result)
|
||||
} else {
|
||||
foundBrace = (char == Scanner.tokenStartDelimiter)
|
||||
foundBrace = (char == Self.tokenStartDelimiter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,4 +227,5 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Location in some content (text)
|
||||
public typealias ContentLocation = (content: String, lineNumber: UInt, lineOffset: Int)
|
||||
@@ -1,12 +1,16 @@
|
||||
import Foundation
|
||||
import PathKit
|
||||
|
||||
/// Type used for loading a template
|
||||
public protocol Loader {
|
||||
/// Load a template with the given name
|
||||
func loadTemplate(name: String, environment: Environment) throws -> Template
|
||||
/// Load a template with the given list of names
|
||||
func loadTemplate(names: [String], environment: Environment) throws -> Template
|
||||
}
|
||||
|
||||
extension Loader {
|
||||
/// Default implementation, tries to load the first template that exists from the list of given names
|
||||
public func loadTemplate(names: [String], environment: Environment) throws -> Template {
|
||||
for name in names {
|
||||
do {
|
||||
@@ -31,13 +35,13 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
|
||||
}
|
||||
|
||||
public init(bundle: [Bundle]) {
|
||||
self.paths = bundle.map {
|
||||
Path($0.bundlePath)
|
||||
self.paths = bundle.map { bundle in
|
||||
Path(bundle.bundlePath)
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "FileSystemLoader(\(paths))"
|
||||
"FileSystemLoader(\(paths))"
|
||||
}
|
||||
|
||||
public func loadTemplate(name: String, environment: Environment) throws -> Template {
|
||||
@@ -119,6 +123,6 @@ class SuspiciousFileOperation: Error {
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "Path `\(path)` is located outside of base path `\(basePath)`"
|
||||
"Path `\(path)` is located outside of base path `\(basePath)`"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
/// Represents a parsed node
|
||||
public protocol NodeType {
|
||||
/// Render the node in the given context
|
||||
func render(_ context: Context) throws -> String
|
||||
@@ -10,17 +11,18 @@ public protocol NodeType {
|
||||
|
||||
/// Render the collection of nodes in the given context
|
||||
public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String {
|
||||
return try nodes
|
||||
.map {
|
||||
try nodes
|
||||
.map { node in
|
||||
do {
|
||||
return try $0.render(context)
|
||||
return try node.render(context)
|
||||
} catch {
|
||||
throw error.withToken($0.token)
|
||||
throw error.withToken(node.token)
|
||||
}
|
||||
}
|
||||
.joined()
|
||||
}
|
||||
|
||||
/// Simple node, used for triggering a closure during rendering
|
||||
public class SimpleNode: NodeType {
|
||||
public let handler: (Context) throws -> String
|
||||
public let token: Token?
|
||||
@@ -31,10 +33,11 @@ public class SimpleNode: NodeType {
|
||||
}
|
||||
|
||||
public func render(_ context: Context) throws -> String {
|
||||
return try handler(context)
|
||||
try handler(context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a block of text, renders the text
|
||||
public class TextNode: NodeType {
|
||||
public let text: String
|
||||
public let token: Token?
|
||||
@@ -45,14 +48,17 @@ public class TextNode: NodeType {
|
||||
}
|
||||
|
||||
public func render(_ context: Context) throws -> String {
|
||||
return self.text
|
||||
self.text
|
||||
}
|
||||
}
|
||||
|
||||
/// Representing something that can be resolved in a context
|
||||
public protocol Resolvable {
|
||||
/// Try to resolve this with the given context
|
||||
func resolve(_ context: Context) throws -> Any?
|
||||
}
|
||||
|
||||
/// Represents a variable, renders the variable, may have conditional expressions.
|
||||
public class VariableNode: NodeType {
|
||||
public let variable: Resolvable
|
||||
public var token: Token?
|
||||
@@ -63,7 +69,7 @@ public class VariableNode: NodeType {
|
||||
let components = token.components
|
||||
|
||||
func hasToken(_ token: String, at index: Int) -> Bool {
|
||||
return components.count > (index + 1) && components[index] == token
|
||||
components.count > (index + 1) && components[index] == token
|
||||
}
|
||||
|
||||
let condition: Expression?
|
||||
@@ -137,7 +143,7 @@ func stringify(_ result: Any?) -> String {
|
||||
}
|
||||
|
||||
func unwrap(_ array: [Any?]) -> [Any] {
|
||||
return array.map { (item: Any?) -> Any in
|
||||
array.map { (item: Any?) -> Any in
|
||||
if let item = item {
|
||||
if let items = item as? [Any?] {
|
||||
return unwrap(items)
|
||||
@@ -1,5 +1,7 @@
|
||||
/// Creates a checker that will stop parsing if it encounters a list of tags.
|
||||
/// Useful for example for scanning until a given "end"-node.
|
||||
public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
|
||||
return { _, token in
|
||||
{ _, token in
|
||||
if let name = token.components.first {
|
||||
for tag in tags where name == tag {
|
||||
return true
|
||||
@@ -12,11 +14,13 @@ public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
|
||||
|
||||
/// A class for parsing an array of tokens and converts them into a collection of Node's
|
||||
public class TokenParser {
|
||||
/// Parser for finding a kind of node
|
||||
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||
|
||||
fileprivate var tokens: [Token]
|
||||
fileprivate let environment: Environment
|
||||
|
||||
/// Simple initializer
|
||||
public init(tokens: [Token], environment: Environment) {
|
||||
self.tokens = tokens
|
||||
self.environment = environment
|
||||
@@ -24,9 +28,11 @@ public class TokenParser {
|
||||
|
||||
/// Parse the given tokens into nodes
|
||||
public func parse() throws -> [NodeType] {
|
||||
return try parse(nil)
|
||||
try parse(nil)
|
||||
}
|
||||
|
||||
/// Parse nodes until a specific "something" is detected, determined by the provided closure.
|
||||
/// Combine this with the `until(:)` function above to scan nodes until a given token.
|
||||
public func parse(_ parseUntil: ((_ parser: TokenParser, _ token: Token) -> (Bool))?) throws -> [NodeType] {
|
||||
var nodes = [NodeType]()
|
||||
|
||||
@@ -61,6 +67,7 @@ public class TokenParser {
|
||||
return nodes
|
||||
}
|
||||
|
||||
/// Pop the next token (returning it)
|
||||
public func nextToken() -> Token? {
|
||||
if !tokens.isEmpty {
|
||||
return tokens.remove(at: 0)
|
||||
@@ -69,23 +76,24 @@ public class TokenParser {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Insert a token
|
||||
public func prependToken(_ token: Token) {
|
||||
tokens.insert(token, at: 0)
|
||||
}
|
||||
|
||||
/// Create filter expression from a string contained in provided token
|
||||
public func compileFilter(_ filterToken: String, containedIn token: Token) throws -> Resolvable {
|
||||
return try environment.compileFilter(filterToken, containedIn: token)
|
||||
try environment.compileFilter(filterToken, containedIn: token)
|
||||
}
|
||||
|
||||
/// Create boolean expression from components contained in provided token
|
||||
public func compileExpression(components: [String], token: Token) throws -> Expression {
|
||||
return try environment.compileExpression(components: components, containedIn: token)
|
||||
try environment.compileExpression(components: components, containedIn: token)
|
||||
}
|
||||
|
||||
/// Create resolvable (i.e. range variable or filter expression) from a string contained in provided token
|
||||
public func compileResolvable(_ token: String, containedIn containingToken: Token) throws -> Resolvable {
|
||||
return try environment.compileResolvable(token, containedIn: containingToken)
|
||||
try environment.compileResolvable(token, containedIn: containingToken)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,10 +119,12 @@ extension Environment {
|
||||
if suggestedFilters.isEmpty {
|
||||
throw TemplateSyntaxError("Unknown filter '\(name)'.")
|
||||
} else {
|
||||
throw TemplateSyntaxError("""
|
||||
throw TemplateSyntaxError(
|
||||
"""
|
||||
Unknown filter '\(name)'. \
|
||||
Found similar filters: \(suggestedFilters.map { "'\($0)'" }.joined(separator: ", ")).
|
||||
""")
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,9 +132,9 @@ extension Environment {
|
||||
let allFilters = extensions.flatMap { $0.filters.keys }
|
||||
|
||||
let filtersWithDistance = allFilters
|
||||
.map { (filterName: $0, distance: $0.levenshteinDistance(name)) }
|
||||
// do not suggest filters which names are shorter than the distance
|
||||
.filter { $0.filterName.count > $0.distance }
|
||||
.map { (filterName: $0, distance: $0.levenshteinDistance(name)) }
|
||||
// do not suggest filters which names are shorter than the distance
|
||||
.filter { $0.filterName.count > $0.distance }
|
||||
guard let minDistance = filtersWithDistance.min(by: { $0.distance < $1.distance })?.distance else {
|
||||
return []
|
||||
}
|
||||
@@ -134,7 +144,7 @@ extension Environment {
|
||||
|
||||
/// Create filter expression from a string
|
||||
public func compileFilter(_ token: String) throws -> Resolvable {
|
||||
return try FilterExpression(token: token, environment: self)
|
||||
try FilterExpression(token: token, environment: self)
|
||||
}
|
||||
|
||||
/// Create filter expression from a string contained in provided token
|
||||
@@ -165,26 +175,26 @@ extension Environment {
|
||||
|
||||
/// Create resolvable (i.e. range variable or filter expression) from a string
|
||||
public func compileResolvable(_ token: String) throws -> Resolvable {
|
||||
return try RangeVariable(token, environment: self)
|
||||
try RangeVariable(token, environment: self)
|
||||
?? compileFilter(token)
|
||||
}
|
||||
|
||||
/// Create resolvable (i.e. range variable or filter expression) from a string contained in provided token
|
||||
public func compileResolvable(_ token: String, containedIn containingToken: Token) throws -> Resolvable {
|
||||
return try RangeVariable(token, environment: self, containedIn: containingToken)
|
||||
?? compileFilter(token, containedIn: containingToken)
|
||||
try RangeVariable(token, environment: self, containedIn: containingToken)
|
||||
?? compileFilter(token, containedIn: containingToken)
|
||||
}
|
||||
|
||||
/// Create boolean expression from components contained in provided token
|
||||
public func compileExpression(components: [String], containedIn token: Token) throws -> Expression {
|
||||
return try IfExpressionParser.parser(components: components, environment: self, token: token).parse()
|
||||
try IfExpressionParser.parser(components: components, environment: self, token: token).parse()
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
|
||||
extension String {
|
||||
subscript(_ index: Int) -> Character {
|
||||
return self[self.index(self.startIndex, offsetBy: index)]
|
||||
self[self.index(self.startIndex, offsetBy: index)]
|
||||
}
|
||||
|
||||
func levenshteinDistance(_ target: String) -> Int {
|
||||
@@ -2,6 +2,7 @@ import Foundation
|
||||
import PathKit
|
||||
|
||||
#if os(Linux)
|
||||
// swiftlint:disable:next prefixed_toplevel_constant
|
||||
let NSFileNoSuchFileError = 4
|
||||
#endif
|
||||
|
||||
@@ -77,6 +78,6 @@ open class Template: ExpressibleByStringLiteral {
|
||||
// swiftlint:disable discouraged_optional_collection
|
||||
/// Render the given template
|
||||
open func render(_ dictionary: [String: Any]? = nil) throws -> String {
|
||||
return try render(Context(dictionary: dictionary ?? [:], environment: environment))
|
||||
try render(Context(dictionary: dictionary ?? [:], environment: environment))
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ extension String {
|
||||
if character == separate {
|
||||
if separate != separator {
|
||||
word.append(separate)
|
||||
} else if (singleQuoteCount % 2 == 0 || doubleQuoteCount % 2 == 0) && !word.isEmpty {
|
||||
} else if (singleQuoteCount.isMultiple(of: 2) || doubleQuoteCount.isMultiple(of: 2)) && !word.isEmpty {
|
||||
appendWord(word, to: &components)
|
||||
word = ""
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public struct SourceMap: Equatable {
|
||||
static let unknown = SourceMap()
|
||||
|
||||
public static func == (lhs: SourceMap, rhs: SourceMap) -> Bool {
|
||||
return lhs.filename == rhs.filename && lhs.location == rhs.location
|
||||
lhs.filename == rhs.filename && lhs.location == rhs.location
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,25 +106,25 @@ public class Token: Equatable {
|
||||
|
||||
/// A token representing a piece of text.
|
||||
public static func text(value: String, at sourceMap: SourceMap) -> Token {
|
||||
return Token(contents: value, kind: .text, sourceMap: sourceMap)
|
||||
Token(contents: value, kind: .text, sourceMap: sourceMap)
|
||||
}
|
||||
|
||||
/// A token representing a variable.
|
||||
public static func variable(value: String, at sourceMap: SourceMap) -> Token {
|
||||
return Token(contents: value, kind: .variable, sourceMap: sourceMap)
|
||||
Token(contents: value, kind: .variable, sourceMap: sourceMap)
|
||||
}
|
||||
|
||||
/// A token representing a comment.
|
||||
public static func comment(value: String, at sourceMap: SourceMap) -> Token {
|
||||
return Token(contents: value, kind: .comment, sourceMap: sourceMap)
|
||||
Token(contents: value, kind: .comment, sourceMap: sourceMap)
|
||||
}
|
||||
|
||||
/// A token representing a template block.
|
||||
public static func block(value: String, at sourceMap: SourceMap) -> Token {
|
||||
return Token(contents: value, kind: .block, sourceMap: sourceMap)
|
||||
Token(contents: value, kind: .block, sourceMap: sourceMap)
|
||||
}
|
||||
|
||||
public static func == (lhs: Token, rhs: Token) -> Bool {
|
||||
return lhs.contents == rhs.contents && lhs.kind == rhs.kind && lhs.sourceMap == rhs.sourceMap
|
||||
lhs.contents == rhs.contents && lhs.kind == rhs.kind && lhs.sourceMap == rhs.sourceMap
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,8 @@ class FilterExpression: Resolvable {
|
||||
let filterBits = bits[bits.indices.suffix(from: 1)]
|
||||
|
||||
do {
|
||||
filters = try filterBits.map {
|
||||
let (name, arguments) = parseFilterComponents(token: $0)
|
||||
filters = try filterBits.map { bit in
|
||||
let (name, arguments) = parseFilterComponents(token: bit)
|
||||
let filter = try environment.findFilter(name)
|
||||
return (filter, arguments)
|
||||
}
|
||||
@@ -208,13 +208,14 @@ protocol Normalizable {
|
||||
|
||||
extension Array: Normalizable {
|
||||
func normalize() -> Any? {
|
||||
return map { $0 as Any }
|
||||
map { $0 as Any }
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next legacy_objc_type
|
||||
extension NSArray: Normalizable {
|
||||
func normalize() -> Any? {
|
||||
return map { $0 as Any }
|
||||
map { $0 as Any }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,8 +274,10 @@ protocol AnyOptional {
|
||||
extension Optional: AnyOptional {
|
||||
var wrapped: Any? {
|
||||
switch self {
|
||||
case let .some(value): return value
|
||||
case .none: return nil
|
||||
case let .some(value):
|
||||
return value
|
||||
case .none:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
#if !swift(>=4.2)
|
||||
extension ArraySlice where Element: Equatable {
|
||||
func firstIndex(of element: Element) -> Int? {
|
||||
return index(of: element)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -16,7 +16,7 @@
|
||||
"tag": "0.14.2"
|
||||
},
|
||||
"source_files": [
|
||||
"Sources/*.swift"
|
||||
"Sources/Stencil/*.swift"
|
||||
],
|
||||
"platforms": {
|
||||
"ios": "8.0",
|
||||
@@ -25,7 +25,6 @@
|
||||
},
|
||||
"cocoapods_version": ">= 1.7.0",
|
||||
"swift_versions": [
|
||||
"4.2",
|
||||
"5.0"
|
||||
],
|
||||
"requires_arc": true,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import XCTest
|
||||
|
||||
import StencilTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += StencilTests.__allTests()
|
||||
|
||||
XCTMain(tests)
|
||||
@@ -4,35 +4,35 @@ import XCTest
|
||||
|
||||
final class ContextTests: XCTestCase {
|
||||
func testContextSubscripting() {
|
||||
describe("Context Subscripting") {
|
||||
describe("Context Subscripting") { test in
|
||||
var context = Context()
|
||||
$0.before {
|
||||
test.before {
|
||||
context = Context(dictionary: ["name": "Kyle"])
|
||||
}
|
||||
|
||||
$0.it("allows you to get a value via subscripting") {
|
||||
test.it("allows you to get a value via subscripting") {
|
||||
try expect(context["name"] as? String) == "Kyle"
|
||||
}
|
||||
|
||||
$0.it("allows you to set a value via subscripting") {
|
||||
test.it("allows you to set a value via subscripting") {
|
||||
context["name"] = "Katie"
|
||||
|
||||
try expect(context["name"] as? String) == "Katie"
|
||||
}
|
||||
|
||||
$0.it("allows you to remove a value via subscripting") {
|
||||
test.it("allows you to remove a value via subscripting") {
|
||||
context["name"] = nil
|
||||
|
||||
try expect(context["name"]).to.beNil()
|
||||
}
|
||||
|
||||
$0.it("allows you to retrieve a value from a parent") {
|
||||
test.it("allows you to retrieve a value from a parent") {
|
||||
try context.push {
|
||||
try expect(context["name"] as? String) == "Kyle"
|
||||
}
|
||||
}
|
||||
|
||||
$0.it("allows you to override a parent's value") {
|
||||
test.it("allows you to override a parent's value") {
|
||||
try context.push {
|
||||
context["name"] = "Katie"
|
||||
try expect(context["name"] as? String) == "Katie"
|
||||
@@ -42,13 +42,13 @@ final class ContextTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testContextRestoration() {
|
||||
describe("Context Restoration") {
|
||||
describe("Context Restoration") { test in
|
||||
var context = Context()
|
||||
$0.before {
|
||||
test.before {
|
||||
context = Context(dictionary: ["name": "Kyle"])
|
||||
}
|
||||
|
||||
$0.it("allows you to pop to restore previous state") {
|
||||
test.it("allows you to pop to restore previous state") {
|
||||
context.push {
|
||||
context["name"] = "Katie"
|
||||
}
|
||||
@@ -56,7 +56,7 @@ final class ContextTests: XCTestCase {
|
||||
try expect(context["name"] as? String) == "Kyle"
|
||||
}
|
||||
|
||||
$0.it("allows you to remove a parent's value in a level") {
|
||||
test.it("allows you to remove a parent's value in a level") {
|
||||
try context.push {
|
||||
context["name"] = nil
|
||||
try expect(context["name"]).to.beNil()
|
||||
@@ -65,7 +65,7 @@ final class ContextTests: XCTestCase {
|
||||
try expect(context["name"] as? String) == "Kyle"
|
||||
}
|
||||
|
||||
$0.it("allows you to push a dictionary and run a closure then restoring previous state") {
|
||||
test.it("allows you to push a dictionary and run a closure then restoring previous state") {
|
||||
var didRun = false
|
||||
|
||||
try context.push(dictionary: ["name": "Katie"]) {
|
||||
@@ -77,7 +77,7 @@ final class ContextTests: XCTestCase {
|
||||
try expect(context["name"] as? String) == "Kyle"
|
||||
}
|
||||
|
||||
$0.it("allows you to flatten the context contents") {
|
||||
test.it("allows you to flatten the context contents") {
|
||||
try context.push(dictionary: ["test": "abc"]) {
|
||||
let flattened = context.flatten()
|
||||
|
||||
|
||||
125
Tests/StencilTests/EnvironmentBaseAndChildTemplateSpec.swift
Normal file
125
Tests/StencilTests/EnvironmentBaseAndChildTemplateSpec.swift
Normal file
@@ -0,0 +1,125 @@
|
||||
import PathKit
|
||||
import Spectre
|
||||
@testable import Stencil
|
||||
import XCTest
|
||||
|
||||
final class EnvironmentBaseAndChildTemplateTests: XCTestCase {
|
||||
private var environment = Environment(loader: ExampleLoader())
|
||||
private var childTemplate: Template = ""
|
||||
private var baseTemplate: Template = ""
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let path = Path(#file as String) + ".." + "fixtures"
|
||||
let loader = FileSystemLoader(paths: [path])
|
||||
environment = Environment(loader: loader)
|
||||
childTemplate = ""
|
||||
baseTemplate = ""
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testSyntaxErrorInBaseTemplate() throws {
|
||||
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
|
||||
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
|
||||
|
||||
try expectError(
|
||||
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
childToken: "extends \"invalid-base.html\"",
|
||||
baseToken: "target|unknown"
|
||||
)
|
||||
}
|
||||
|
||||
func testRuntimeErrorInBaseTemplate() throws {
|
||||
let filterExtension = Extension()
|
||||
filterExtension.registerFilter("unknown") { (_: Any?) in
|
||||
throw TemplateSyntaxError("filter error")
|
||||
}
|
||||
environment.extensions += [filterExtension]
|
||||
|
||||
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
|
||||
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
|
||||
|
||||
try expectError(
|
||||
reason: "filter error",
|
||||
childToken: "extends \"invalid-base.html\"",
|
||||
baseToken: "target|unknown"
|
||||
)
|
||||
}
|
||||
|
||||
func testSyntaxErrorInChildTemplate() throws {
|
||||
childTemplate = Template(
|
||||
templateString: """
|
||||
{% extends "base.html" %}
|
||||
{% block body %}Child {{ target|unknown }}{% endblock %}
|
||||
""",
|
||||
environment: environment,
|
||||
name: nil
|
||||
)
|
||||
|
||||
try expectError(
|
||||
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
childToken: "target|unknown",
|
||||
baseToken: nil
|
||||
)
|
||||
}
|
||||
|
||||
func testRuntimeErrorInChildTemplate() throws {
|
||||
let filterExtension = Extension()
|
||||
filterExtension.registerFilter("unknown") { (_: Any?) in
|
||||
throw TemplateSyntaxError("filter error")
|
||||
}
|
||||
environment.extensions += [filterExtension]
|
||||
|
||||
childTemplate = Template(
|
||||
templateString: """
|
||||
{% extends "base.html" %}
|
||||
{% block body %}Child {{ target|unknown }}{% endblock %}
|
||||
""",
|
||||
environment: environment,
|
||||
name: nil
|
||||
)
|
||||
|
||||
try expectError(
|
||||
reason: "filter error",
|
||||
childToken: "target|unknown",
|
||||
baseToken: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func expectError(
|
||||
reason: String,
|
||||
childToken: String,
|
||||
baseToken: String?,
|
||||
file: String = #file,
|
||||
line: Int = #line,
|
||||
function: String = #function
|
||||
) throws {
|
||||
var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason)
|
||||
if let baseToken = baseToken {
|
||||
expectedError.stackTrace = [
|
||||
expectedSyntaxError(
|
||||
token: baseToken,
|
||||
template: baseTemplate,
|
||||
description: reason
|
||||
).token
|
||||
].compactMap { $0 }
|
||||
}
|
||||
let error = try expect(
|
||||
self.environment.render(template: self.childTemplate, context: ["target": "World"]),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
).toThrow() as TemplateSyntaxError
|
||||
let reporter = SimpleErrorReporter()
|
||||
try expect(
|
||||
reporter.renderError(error),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
) == reporter.renderError(expectedError)
|
||||
}
|
||||
}
|
||||
88
Tests/StencilTests/EnvironmentIncludeTemplateSpec.swift
Normal file
88
Tests/StencilTests/EnvironmentIncludeTemplateSpec.swift
Normal file
@@ -0,0 +1,88 @@
|
||||
import PathKit
|
||||
import Spectre
|
||||
@testable import Stencil
|
||||
import XCTest
|
||||
|
||||
final class EnvironmentIncludeTemplateTests: XCTestCase {
|
||||
private var environment = Environment(loader: ExampleLoader())
|
||||
private var template: Template = ""
|
||||
private var includedTemplate: Template = ""
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let path = Path(#file as String) + ".." + "fixtures"
|
||||
let loader = FileSystemLoader(paths: [path])
|
||||
environment = Environment(loader: loader)
|
||||
template = ""
|
||||
includedTemplate = ""
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testSyntaxError() throws {
|
||||
template = Template(templateString: """
|
||||
{% include "invalid-include.html" %}
|
||||
""", environment: environment)
|
||||
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
|
||||
|
||||
try expectError(
|
||||
reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
token: #"include "invalid-include.html""#,
|
||||
includedToken: "target|unknown"
|
||||
)
|
||||
}
|
||||
|
||||
func testRuntimeError() throws {
|
||||
let filterExtension = Extension()
|
||||
filterExtension.registerFilter("unknown") { (_: Any?) in
|
||||
throw TemplateSyntaxError("filter error")
|
||||
}
|
||||
environment.extensions += [filterExtension]
|
||||
|
||||
template = Template(templateString: """
|
||||
{% include "invalid-include.html" %}
|
||||
""", environment: environment)
|
||||
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
|
||||
|
||||
try expectError(
|
||||
reason: "filter error",
|
||||
token: "include \"invalid-include.html\"",
|
||||
includedToken: "target|unknown"
|
||||
)
|
||||
}
|
||||
|
||||
private func expectError(
|
||||
reason: String,
|
||||
token: String,
|
||||
includedToken: String,
|
||||
file: String = #file,
|
||||
line: Int = #line,
|
||||
function: String = #function
|
||||
) throws {
|
||||
var expectedError = expectedSyntaxError(token: token, template: template, description: reason)
|
||||
expectedError.stackTrace = [
|
||||
expectedSyntaxError(
|
||||
token: includedToken,
|
||||
template: includedTemplate,
|
||||
description: reason
|
||||
).token
|
||||
].compactMap { $0 }
|
||||
|
||||
let error = try expect(
|
||||
self.environment.render(template: self.template, context: ["target": "World"]),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
).toThrow() as TemplateSyntaxError
|
||||
let reporter = SimpleErrorReporter()
|
||||
try expect(
|
||||
reporter.renderError(error),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
) == reporter.renderError(expectedError)
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import Spectre
|
||||
import XCTest
|
||||
|
||||
final class EnvironmentTests: XCTestCase {
|
||||
var environment = Environment(loader: ExampleLoader())
|
||||
var template: Template = ""
|
||||
private var environment = Environment(loader: ExampleLoader())
|
||||
private var template: Template = ""
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
@@ -26,6 +26,10 @@ final class EnvironmentTests: XCTestCase {
|
||||
template = ""
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testLoading() {
|
||||
it("can load a template from a name") {
|
||||
let template = try self.environment.loadTemplate(name: "example.html")
|
||||
@@ -207,242 +211,11 @@ final class EnvironmentTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
final class EnvironmentIncludeTemplateTests: XCTestCase {
|
||||
var environment = Environment(loader: ExampleLoader())
|
||||
var template: Template = ""
|
||||
var includedTemplate: Template = ""
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let path = Path(#file as String) + ".." + "fixtures"
|
||||
let loader = FileSystemLoader(paths: [path])
|
||||
environment = Environment(loader: loader)
|
||||
template = ""
|
||||
includedTemplate = ""
|
||||
}
|
||||
|
||||
func testSyntaxError() throws {
|
||||
template = Template(templateString: """
|
||||
{% include "invalid-include.html" %}
|
||||
""", environment: environment)
|
||||
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
|
||||
|
||||
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
token: """
|
||||
include "invalid-include.html"
|
||||
""",
|
||||
includedToken: "target|unknown")
|
||||
}
|
||||
|
||||
func testRuntimeError() throws {
|
||||
let filterExtension = Extension()
|
||||
filterExtension.registerFilter("unknown") { (_: Any?) in
|
||||
throw TemplateSyntaxError("filter error")
|
||||
}
|
||||
environment.extensions += [filterExtension]
|
||||
|
||||
template = Template(templateString: """
|
||||
{% include "invalid-include.html" %}
|
||||
""", environment: environment)
|
||||
includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
|
||||
|
||||
try expectError(reason: "filter error",
|
||||
token: "include \"invalid-include.html\"",
|
||||
includedToken: "target|unknown")
|
||||
}
|
||||
|
||||
private func expectError(
|
||||
reason: String,
|
||||
token: String,
|
||||
includedToken: String,
|
||||
file: String = #file,
|
||||
line: Int = #line,
|
||||
function: String = #function
|
||||
) throws {
|
||||
var expectedError = expectedSyntaxError(token: token, template: template, description: reason)
|
||||
expectedError.stackTrace = [
|
||||
expectedSyntaxError(
|
||||
token: includedToken,
|
||||
template: includedTemplate,
|
||||
description: reason
|
||||
).token
|
||||
].compactMap { $0 }
|
||||
|
||||
let error = try expect(
|
||||
self.environment.render(template: self.template, context: ["target": "World"]),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
).toThrow() as TemplateSyntaxError
|
||||
let reporter = SimpleErrorReporter()
|
||||
try expect(
|
||||
reporter.renderError(error),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
) == reporter.renderError(expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
final class EnvironmentBaseAndChildTemplateTests: XCTestCase {
|
||||
var environment = Environment(loader: ExampleLoader())
|
||||
var childTemplate: Template = ""
|
||||
var baseTemplate: Template = ""
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let path = Path(#file as String) + ".." + "fixtures"
|
||||
let loader = FileSystemLoader(paths: [path])
|
||||
environment = Environment(loader: loader)
|
||||
childTemplate = ""
|
||||
baseTemplate = ""
|
||||
}
|
||||
|
||||
func testSyntaxErrorInBaseTemplate() throws {
|
||||
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
|
||||
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
|
||||
|
||||
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
childToken: "extends \"invalid-base.html\"",
|
||||
baseToken: "target|unknown")
|
||||
}
|
||||
|
||||
func testRuntimeErrorInBaseTemplate() throws {
|
||||
let filterExtension = Extension()
|
||||
filterExtension.registerFilter("unknown") { (_: Any?) in
|
||||
throw TemplateSyntaxError("filter error")
|
||||
}
|
||||
environment.extensions += [filterExtension]
|
||||
|
||||
childTemplate = try environment.loadTemplate(name: "invalid-child-super.html")
|
||||
baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
|
||||
|
||||
try expectError(reason: "filter error",
|
||||
childToken: "extends \"invalid-base.html\"",
|
||||
baseToken: "target|unknown")
|
||||
}
|
||||
|
||||
func testSyntaxErrorInChildTemplate() throws {
|
||||
childTemplate = Template(
|
||||
templateString: """
|
||||
{% extends "base.html" %}
|
||||
{% block body %}Child {{ target|unknown }}{% endblock %}
|
||||
""",
|
||||
environment: environment,
|
||||
name: nil
|
||||
)
|
||||
|
||||
try expectError(reason: "Unknown filter 'unknown'. Found similar filters: 'uppercase'.",
|
||||
childToken: "target|unknown",
|
||||
baseToken: nil)
|
||||
}
|
||||
|
||||
func testRuntimeErrorInChildTemplate() throws {
|
||||
let filterExtension = Extension()
|
||||
filterExtension.registerFilter("unknown") { (_: Any?) in
|
||||
throw TemplateSyntaxError("filter error")
|
||||
}
|
||||
environment.extensions += [filterExtension]
|
||||
|
||||
childTemplate = Template(
|
||||
templateString: """
|
||||
{% extends "base.html" %}
|
||||
{% block body %}Child {{ target|unknown }}{% endblock %}
|
||||
""",
|
||||
environment: environment,
|
||||
name: nil
|
||||
)
|
||||
|
||||
try expectError(reason: "filter error",
|
||||
childToken: "target|unknown",
|
||||
baseToken: nil)
|
||||
}
|
||||
|
||||
private func expectError(
|
||||
reason: String,
|
||||
childToken: String,
|
||||
baseToken: String?,
|
||||
file: String = #file,
|
||||
line: Int = #line,
|
||||
function: String = #function
|
||||
) throws {
|
||||
var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason)
|
||||
if let baseToken = baseToken {
|
||||
expectedError.stackTrace = [
|
||||
expectedSyntaxError(
|
||||
token: baseToken,
|
||||
template: baseTemplate,
|
||||
description: reason
|
||||
).token
|
||||
].compactMap { $0 }
|
||||
}
|
||||
let error = try expect(
|
||||
self.environment.render(template: self.childTemplate, context: ["target": "World"]),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
).toThrow() as TemplateSyntaxError
|
||||
let reporter = SimpleErrorReporter()
|
||||
try expect(
|
||||
reporter.renderError(error),
|
||||
file: file,
|
||||
line: line,
|
||||
function: function
|
||||
) == reporter.renderError(expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
extension Expectation {
|
||||
@discardableResult
|
||||
func toThrow<T: Error>() throws -> T {
|
||||
var thrownError: Error?
|
||||
|
||||
do {
|
||||
_ = try expression()
|
||||
} catch {
|
||||
thrownError = error
|
||||
}
|
||||
|
||||
if let thrownError = thrownError {
|
||||
if let thrownError = thrownError as? T {
|
||||
return thrownError
|
||||
} else {
|
||||
throw failure("\(thrownError) is not \(T.self)")
|
||||
}
|
||||
} else {
|
||||
throw failure("expression did not throw an error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension XCTestCase {
|
||||
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
|
||||
guard let range = template.templateString.range(of: token) else {
|
||||
fatalError("Can't find '\(token)' in '\(template)'")
|
||||
}
|
||||
let lexer = Lexer(templateString: template.templateString)
|
||||
let location = lexer.rangeLocation(range)
|
||||
let sourceMap = SourceMap(filename: template.name, location: location)
|
||||
let token = Token.block(value: token, at: sourceMap)
|
||||
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
|
||||
}
|
||||
}
|
||||
|
||||
private class ExampleLoader: Loader {
|
||||
func loadTemplate(name: String, environment: Environment) throws -> Template {
|
||||
if name == "example.html" {
|
||||
return Template(templateString: "Hello World!", environment: environment, name: name)
|
||||
}
|
||||
|
||||
throw TemplateDoesNotExist(templateNames: [name], loader: self)
|
||||
}
|
||||
}
|
||||
// MARK: - Helpers
|
||||
|
||||
private class CustomTemplate: Template {
|
||||
// swiftlint:disable discouraged_optional_collection
|
||||
override func render(_ dictionary: [String: Any]? = nil) throws -> String {
|
||||
return "here"
|
||||
"here"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Spectre
|
||||
import XCTest
|
||||
|
||||
final class ExpressionsTests: XCTestCase {
|
||||
let parser = TokenParser(tokens: [], environment: Environment())
|
||||
private let parser = TokenParser(tokens: [], environment: Environment())
|
||||
|
||||
private func makeExpression(_ components: [String]) -> Expression {
|
||||
do {
|
||||
|
||||
@@ -103,30 +103,30 @@ final class FilterTests: XCTestCase {
|
||||
}
|
||||
|
||||
it("allows you to register a custom filter which accepts several arguments") {
|
||||
let template = Template(templateString: """
|
||||
{{ name|repeat:'value"1"',"value'2'",'(key, value)' }}
|
||||
""")
|
||||
let template = Template(templateString: """
|
||||
{{ name|repeat:'value"1"',"value'2'",'(key, value)' }}
|
||||
""")
|
||||
|
||||
let repeatExtension = Extension()
|
||||
repeatExtension.registerFilter("repeat") { value, arguments in
|
||||
guard let value = value else { return nil }
|
||||
let args = arguments.compactMap { $0 }
|
||||
return "\(value) \(value) with args 0: \(args[0]), 1: \(args[1]), 2: \(args[2])"
|
||||
}
|
||||
let repeatExtension = Extension()
|
||||
repeatExtension.registerFilter("repeat") { value, arguments in
|
||||
guard let value = value else { return nil }
|
||||
let args = arguments.compactMap { $0 }
|
||||
return "\(value) \(value) with args 0: \(args[0]), 1: \(args[1]), 2: \(args[2])"
|
||||
}
|
||||
|
||||
let result = try template.render(Context(
|
||||
dictionary: context,
|
||||
environment: Environment(extensions: [repeatExtension])
|
||||
))
|
||||
try expect(result) == """
|
||||
Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value)
|
||||
"""
|
||||
let result = try template.render(Context(
|
||||
dictionary: context,
|
||||
environment: Environment(extensions: [repeatExtension])
|
||||
))
|
||||
try expect(result) == """
|
||||
Kyle Kyle with args 0: value"1", 1: value'2', 2: (key, value)
|
||||
"""
|
||||
}
|
||||
|
||||
it("allows whitespace in expression") {
|
||||
let template = Template(templateString: """
|
||||
{{ value | join : ", " }}
|
||||
""")
|
||||
{{ value | join : ", " }}
|
||||
""")
|
||||
let result = try template.render(Context(dictionary: ["value": ["One", "Two"]]))
|
||||
try expect(result) == "One, Two"
|
||||
}
|
||||
@@ -363,10 +363,12 @@ final class FilterTests: XCTestCase {
|
||||
Two
|
||||
"""
|
||||
]))
|
||||
// swiftlint:disable indentation_width
|
||||
try expect(result) == """
|
||||
One
|
||||
Two
|
||||
"""
|
||||
// swiftlint:enable indentation_width
|
||||
}
|
||||
|
||||
func testIndentNotEmptyLines() throws {
|
||||
@@ -383,6 +385,7 @@ final class FilterTests: XCTestCase {
|
||||
|
||||
"""
|
||||
]))
|
||||
// swiftlint:disable indentation_width
|
||||
try expect(result) == """
|
||||
One
|
||||
|
||||
@@ -391,6 +394,7 @@ final class FilterTests: XCTestCase {
|
||||
|
||||
|
||||
"""
|
||||
// swiftlint:enable indentation_width
|
||||
}
|
||||
|
||||
func testDynamicFilters() throws {
|
||||
|
||||
@@ -23,9 +23,9 @@ final class FilterTagTests: XCTestCase {
|
||||
|
||||
it("can render filters with arguments") {
|
||||
let ext = Extension()
|
||||
ext.registerFilter("split") {
|
||||
guard let value = $0 as? String,
|
||||
let argument = $1.first as? String else { return $0 }
|
||||
ext.registerFilter("split") { value, args in
|
||||
guard let value = value as? String,
|
||||
let argument = args.first as? String else { return value }
|
||||
return value.components(separatedBy: argument)
|
||||
}
|
||||
let env = Environment(extensions: [ext])
|
||||
@@ -37,11 +37,11 @@ final class FilterTagTests: XCTestCase {
|
||||
|
||||
it("can render filters with quote as an argument") {
|
||||
let ext = Extension()
|
||||
ext.registerFilter("replace") {
|
||||
guard let value = $0 as? String,
|
||||
$1.count == 2,
|
||||
let search = $1.first as? String,
|
||||
let replacement = $1.last as? String else { return $0 }
|
||||
ext.registerFilter("replace") { value, args in
|
||||
guard let value = value as? String,
|
||||
args.count == 2,
|
||||
let search = args.first as? String,
|
||||
let replacement = args.last as? String else { return value }
|
||||
return value.replacingOccurrences(of: search, with: replacement)
|
||||
}
|
||||
let env = Environment(extensions: [ext])
|
||||
|
||||
@@ -3,9 +3,10 @@ import Spectre
|
||||
import XCTest
|
||||
|
||||
final class ForNodeTests: XCTestCase {
|
||||
let context = Context(dictionary: [
|
||||
private let context = Context(dictionary: [
|
||||
"items": [1, 2, 3],
|
||||
"anyItems": [1, 2, 3] as [Any],
|
||||
// swiftlint:disable:next legacy_objc_type
|
||||
"nsItems": NSArray(array: [1, 2, 3]),
|
||||
"emptyItems": [Int](),
|
||||
"dict": [
|
||||
@@ -313,6 +314,8 @@ final class ForNodeTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private struct MyStruct {
|
||||
let string: String
|
||||
let number: Int
|
||||
|
||||
63
Tests/StencilTests/Helpers.swift
Normal file
63
Tests/StencilTests/Helpers.swift
Normal file
@@ -0,0 +1,63 @@
|
||||
import PathKit
|
||||
import Spectre
|
||||
@testable import Stencil
|
||||
import XCTest
|
||||
|
||||
extension Expectation {
|
||||
@discardableResult
|
||||
func toThrow<T: Error>() throws -> T {
|
||||
var thrownError: Error?
|
||||
|
||||
do {
|
||||
_ = try expression()
|
||||
} catch {
|
||||
thrownError = error
|
||||
}
|
||||
|
||||
if let thrownError = thrownError {
|
||||
if let thrownError = thrownError as? T {
|
||||
return thrownError
|
||||
} else {
|
||||
throw failure("\(thrownError) is not \(T.self)")
|
||||
}
|
||||
} else {
|
||||
throw failure("expression did not throw an error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension XCTestCase {
|
||||
func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError {
|
||||
guard let range = template.templateString.range(of: token) else {
|
||||
fatalError("Can't find '\(token)' in '\(template)'")
|
||||
}
|
||||
let lexer = Lexer(templateString: template.templateString)
|
||||
let location = lexer.rangeLocation(range)
|
||||
let sourceMap = SourceMap(filename: template.name, location: location)
|
||||
let token = Token.block(value: token, at: sourceMap)
|
||||
return TemplateSyntaxError(reason: description, token: token, stackTrace: [])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
class ExampleLoader: Loader {
|
||||
func loadTemplate(name: String, environment: Environment) throws -> Template {
|
||||
if name == "example.html" {
|
||||
return Template(templateString: "Hello World!", environment: environment, name: name)
|
||||
}
|
||||
|
||||
throw TemplateDoesNotExist(templateNames: [name], loader: self)
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorNode: NodeType {
|
||||
let token: Token?
|
||||
init(token: Token? = nil) {
|
||||
self.token = token
|
||||
}
|
||||
|
||||
func render(_ context: Context) throws -> String {
|
||||
throw TemplateSyntaxError("Custom Error")
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,6 @@ import Spectre
|
||||
@testable import Stencil
|
||||
import XCTest
|
||||
|
||||
private struct SomeType {
|
||||
let value: String? = nil
|
||||
}
|
||||
|
||||
final class IfNodeTests: XCTestCase {
|
||||
func testParseIf() {
|
||||
it("can parse an if block") {
|
||||
@@ -286,3 +282,9 @@ final class IfNodeTests: XCTestCase {
|
||||
try expect(renderNodes(nodes, Context(dictionary: ["value": 4]))) == "false"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private struct SomeType {
|
||||
let value: String? = nil
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import Spectre
|
||||
import XCTest
|
||||
|
||||
final class IncludeTests: XCTestCase {
|
||||
let path = Path(#file as String) + ".." + "fixtures"
|
||||
lazy var loader = FileSystemLoader(paths: [path])
|
||||
lazy var environment = Environment(loader: loader)
|
||||
private let path = Path(#file as String) + ".." + "fixtures"
|
||||
private lazy var loader = FileSystemLoader(paths: [path])
|
||||
private lazy var environment = Environment(loader: loader)
|
||||
|
||||
func testParsing() {
|
||||
it("throws an error when no template is given") {
|
||||
|
||||
@@ -4,9 +4,9 @@ import Stencil
|
||||
import XCTest
|
||||
|
||||
final class InheritanceTests: XCTestCase {
|
||||
let path = Path(#file as String) + ".." + "fixtures"
|
||||
lazy var loader = FileSystemLoader(paths: [path])
|
||||
lazy var environment = Environment(loader: loader)
|
||||
private let path = Path(#file as String) + ".." + "fixtures"
|
||||
private lazy var loader = FileSystemLoader(paths: [path])
|
||||
private lazy var environment = Environment(loader: loader)
|
||||
|
||||
func testInheritance() {
|
||||
it("can inherit from another template") {
|
||||
|
||||
@@ -82,6 +82,7 @@ final class LexerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testNewlines() throws {
|
||||
// swiftlint:disable indentation_width
|
||||
let templateString = """
|
||||
My name is {%
|
||||
if name
|
||||
@@ -92,6 +93,7 @@ final class LexerTests: XCTestCase {
|
||||
}}{%
|
||||
endif %}.
|
||||
"""
|
||||
// swiftlint:enable indentation_width
|
||||
let lexer = Lexer(templateString: templateString)
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ final class TemplateLoaderTests: XCTestCase {
|
||||
|
||||
func testDictionaryLoader() {
|
||||
let loader = DictionaryLoader(templates: [
|
||||
"index.html": "Hello World"
|
||||
"index.html": "Hello World"
|
||||
])
|
||||
let environment = Environment(loader: loader)
|
||||
|
||||
|
||||
@@ -2,19 +2,8 @@ import Spectre
|
||||
@testable import Stencil
|
||||
import XCTest
|
||||
|
||||
class ErrorNode: NodeType {
|
||||
let token: Token?
|
||||
init(token: Token? = nil) {
|
||||
self.token = token
|
||||
}
|
||||
|
||||
func render(_ context: Context) throws -> String {
|
||||
throw TemplateSyntaxError("Custom Error")
|
||||
}
|
||||
}
|
||||
|
||||
final class NodeTests: XCTestCase {
|
||||
let context = Context(dictionary: [
|
||||
private let context = Context(dictionary: [
|
||||
"name": "Kyle",
|
||||
"age": 27,
|
||||
"items": [1, 2, 3]
|
||||
|
||||
@@ -5,9 +5,9 @@ import XCTest
|
||||
final class NowNodeTests: XCTestCase {
|
||||
func testParsing() {
|
||||
it("parses default format without any now arguments") {
|
||||
#if os(Linux)
|
||||
#if os(Linux)
|
||||
throw skip()
|
||||
#else
|
||||
#else
|
||||
let tokens: [Token] = [ .block(value: "now", at: .unknown) ]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
|
||||
@@ -15,36 +15,36 @@ final class NowNodeTests: XCTestCase {
|
||||
let node = nodes.first as? NowNode
|
||||
try expect(nodes.count) == 1
|
||||
try expect(node?.format.variable) == "\"yyyy-MM-dd 'at' HH:mm\""
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
it("parses now with a format") {
|
||||
#if os(Linux)
|
||||
#if os(Linux)
|
||||
throw skip()
|
||||
#else
|
||||
#else
|
||||
let tokens: [Token] = [ .block(value: "now \"HH:mm\"", at: .unknown) ]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? NowNode
|
||||
try expect(nodes.count) == 1
|
||||
try expect(node?.format.variable) == "\"HH:mm\""
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func testRendering() {
|
||||
it("renders the date") {
|
||||
#if os(Linux)
|
||||
#if os(Linux)
|
||||
throw skip()
|
||||
#else
|
||||
#else
|
||||
let node = NowNode(format: Variable("\"yyyy-MM-dd\""))
|
||||
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
let date = formatter.string(from: NSDate() as Date)
|
||||
let date = formatter.string(from: Date())
|
||||
|
||||
try expect(try node.render(Context())) == date
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ final class TokenParserTests: XCTestCase {
|
||||
|
||||
try expect(try parser.parse()).toThrow(TemplateSyntaxError(
|
||||
reason: "Unknown template tag 'unknown'",
|
||||
token: tokens.first)
|
||||
)
|
||||
token: tokens.first
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,8 @@ import Spectre
|
||||
import Stencil
|
||||
import XCTest
|
||||
|
||||
private struct CustomNode: NodeType {
|
||||
let token: Token?
|
||||
func render(_ context: Context) throws -> String {
|
||||
return "Hello World"
|
||||
}
|
||||
}
|
||||
|
||||
private struct Article {
|
||||
let title: String
|
||||
let author: String
|
||||
}
|
||||
|
||||
final class StencilTests: XCTestCase {
|
||||
lazy var environment: Environment = {
|
||||
private lazy var environment: Environment = {
|
||||
let exampleExtension = Extension()
|
||||
exampleExtension.registerSimpleTag("simpletag") { _ in
|
||||
"Hello World"
|
||||
@@ -32,7 +20,7 @@ final class StencilTests: XCTestCase {
|
||||
There are {{ articles.count }} articles.
|
||||
|
||||
{% for article in articles %}\
|
||||
- {{ article.title }} by {{ article.author }}.
|
||||
- {{ article.title }} by {{ article.author }}.
|
||||
{% endfor %}
|
||||
"""
|
||||
|
||||
@@ -49,8 +37,8 @@ final class StencilTests: XCTestCase {
|
||||
try expect(result) == """
|
||||
There are 2 articles.
|
||||
|
||||
- Migrating from OCUnit to XCTest by Kyle Fuller.
|
||||
- Memory Management with ARC by Kyle Fuller.
|
||||
- Migrating from OCUnit to XCTest by Kyle Fuller.
|
||||
- Memory Management with ARC by Kyle Fuller.
|
||||
|
||||
"""
|
||||
}
|
||||
@@ -66,3 +54,17 @@ final class StencilTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private struct CustomNode: NodeType {
|
||||
let token: Token?
|
||||
func render(_ context: Context) throws -> String {
|
||||
"Hello World"
|
||||
}
|
||||
}
|
||||
|
||||
private struct Article {
|
||||
let title: String
|
||||
let author: String
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ final class TemplateTests: XCTestCase {
|
||||
}
|
||||
|
||||
it("can render a template from a string literal") {
|
||||
let template: Template = "Hello World"
|
||||
let result = try template.render([ "name": "Kyle" ])
|
||||
try expect(result) == "Hello World"
|
||||
let template: Template = "Hello World"
|
||||
let result = try template.render([ "name": "Kyle" ])
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,47 +2,8 @@ import Spectre
|
||||
@testable import Stencil
|
||||
import XCTest
|
||||
|
||||
#if os(OSX)
|
||||
@objc
|
||||
class Superclass: NSObject {
|
||||
@objc let name = "Foo"
|
||||
}
|
||||
@objc
|
||||
class Object: Superclass {
|
||||
@objc let title = "Hello World"
|
||||
}
|
||||
#endif
|
||||
|
||||
private struct Person {
|
||||
let name: String
|
||||
}
|
||||
|
||||
private struct Article {
|
||||
let author: Person
|
||||
}
|
||||
|
||||
private class WebSite {
|
||||
let url: String = "blog.com"
|
||||
}
|
||||
|
||||
private class Blog: WebSite {
|
||||
let articles: [Article] = [Article(author: Person(name: "Kyle"))]
|
||||
let featuring: Article? = Article(author: Person(name: "Jhon"))
|
||||
}
|
||||
|
||||
@dynamicMemberLookup
|
||||
private struct DynamicStruct: DynamicMemberLookup {
|
||||
subscript(dynamicMember member: String) -> Any? {
|
||||
member == "test" ? "this is a dynamic response" : nil
|
||||
}
|
||||
}
|
||||
|
||||
private enum DynamicEnum: String, DynamicMemberLookup {
|
||||
case someValue = "this is raw value"
|
||||
}
|
||||
|
||||
final class VariableTests: XCTestCase {
|
||||
let context: Context = {
|
||||
private let context: Context = {
|
||||
let ext = Extension()
|
||||
ext.registerFilter("incr") { arg in
|
||||
(arg.flatMap { toNumber(value: $0) } ?? 0) + 1
|
||||
@@ -66,9 +27,9 @@ final class VariableTests: XCTestCase {
|
||||
"struct": DynamicStruct()
|
||||
]
|
||||
], environment: environment)
|
||||
#if os(OSX)
|
||||
#if os(OSX)
|
||||
context["object"] = Object()
|
||||
#endif
|
||||
#endif
|
||||
return context
|
||||
}()
|
||||
|
||||
@@ -145,9 +106,9 @@ final class VariableTests: XCTestCase {
|
||||
let result = try variable.resolve(self.context) as? String
|
||||
try expect(result) == "Katie"
|
||||
|
||||
let variable1 = Variable("contacts.1")
|
||||
let result1 = try variable1.resolve(self.context) as? String
|
||||
try expect(result1) == "Carlton"
|
||||
let variable1 = Variable("contacts.1")
|
||||
let result1 = try variable1.resolve(self.context) as? String
|
||||
try expect(result1) == "Carlton"
|
||||
}
|
||||
|
||||
it("can resolve an item from an array via unknown index") {
|
||||
@@ -214,7 +175,7 @@ final class VariableTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testKVO() {
|
||||
#if os(OSX)
|
||||
#if os(OSX)
|
||||
it("can resolve a value via KVO") {
|
||||
let variable = Variable("object.title")
|
||||
let result = try variable.resolve(self.context) as? String
|
||||
@@ -232,7 +193,7 @@ final class VariableTests: XCTestCase {
|
||||
let result = try variable.resolve(self.context) as? String
|
||||
try expect(result).to.beNil()
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
func testTuple() {
|
||||
@@ -285,7 +246,7 @@ final class VariableTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
#if os(OSX)
|
||||
#if os(OSX)
|
||||
it("can resolve a subscript via KVO") {
|
||||
try self.context.push(dictionary: ["property": "name"]) {
|
||||
let variable = Variable("object[property]")
|
||||
@@ -293,7 +254,7 @@ final class VariableTests: XCTestCase {
|
||||
try expect(result) == "Foo"
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
it("can resolve an optional subscript via reflection") {
|
||||
try self.context.push(dictionary: ["property": "featuring"]) {
|
||||
@@ -394,3 +355,44 @@ final class VariableTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
#if os(OSX)
|
||||
@objc
|
||||
class Superclass: NSObject {
|
||||
@objc let name = "Foo"
|
||||
}
|
||||
@objc
|
||||
class Object: Superclass {
|
||||
@objc let title = "Hello World"
|
||||
}
|
||||
#endif
|
||||
|
||||
private struct Person {
|
||||
let name: String
|
||||
}
|
||||
|
||||
private struct Article {
|
||||
let author: Person
|
||||
}
|
||||
|
||||
private class WebSite {
|
||||
let url: String = "blog.com"
|
||||
}
|
||||
|
||||
private class Blog: WebSite {
|
||||
let articles: [Article] = [Article(author: Person(name: "Kyle"))]
|
||||
let featuring: Article? = Article(author: Person(name: "Jhon"))
|
||||
}
|
||||
|
||||
@dynamicMemberLookup
|
||||
private struct DynamicStruct: DynamicMemberLookup {
|
||||
subscript(dynamicMember member: String) -> Any? {
|
||||
member == "test" ? "this is a dynamic response" : nil
|
||||
}
|
||||
}
|
||||
|
||||
private enum DynamicEnum: String, DynamicMemberLookup {
|
||||
case someValue = "this is raw value"
|
||||
}
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
import XCTest
|
||||
|
||||
extension ContextTests {
|
||||
static let __allTests = [
|
||||
("testContextRestoration", testContextRestoration),
|
||||
("testContextSubscripting", testContextSubscripting),
|
||||
]
|
||||
}
|
||||
|
||||
extension EnvironmentBaseAndChildTemplateTests {
|
||||
static let __allTests = [
|
||||
("testRuntimeErrorInBaseTemplate", testRuntimeErrorInBaseTemplate),
|
||||
("testRuntimeErrorInChildTemplate", testRuntimeErrorInChildTemplate),
|
||||
("testSyntaxErrorInBaseTemplate", testSyntaxErrorInBaseTemplate),
|
||||
("testSyntaxErrorInChildTemplate", testSyntaxErrorInChildTemplate),
|
||||
]
|
||||
}
|
||||
|
||||
extension EnvironmentIncludeTemplateTests {
|
||||
static let __allTests = [
|
||||
("testRuntimeError", testRuntimeError),
|
||||
("testSyntaxError", testSyntaxError),
|
||||
]
|
||||
}
|
||||
|
||||
extension EnvironmentTests {
|
||||
static let __allTests = [
|
||||
("testLoading", testLoading),
|
||||
("testRendering", testRendering),
|
||||
("testRenderingError", testRenderingError),
|
||||
("testSyntaxError", testSyntaxError),
|
||||
("testUnknownFilter", testUnknownFilter),
|
||||
]
|
||||
}
|
||||
|
||||
extension ExpressionsTests {
|
||||
static let __allTests = [
|
||||
("testAndExpression", testAndExpression),
|
||||
("testEqualityExpression", testEqualityExpression),
|
||||
("testExpressionParsing", testExpressionParsing),
|
||||
("testFalseExpressions", testFalseExpressions),
|
||||
("testFalseInExpression", testFalseInExpression),
|
||||
("testInequalityExpression", testInequalityExpression),
|
||||
("testLessThanEqualExpression", testLessThanEqualExpression),
|
||||
("testLessThanExpression", testLessThanExpression),
|
||||
("testMoreThanEqualExpression", testMoreThanEqualExpression),
|
||||
("testMoreThanExpression", testMoreThanExpression),
|
||||
("testMultipleExpressions", testMultipleExpressions),
|
||||
("testNotExpression", testNotExpression),
|
||||
("testOrExpression", testOrExpression),
|
||||
("testTrueExpressions", testTrueExpressions),
|
||||
("testTrueInExpression", testTrueInExpression),
|
||||
]
|
||||
}
|
||||
|
||||
extension FilterTagTests {
|
||||
static let __allTests = [
|
||||
("testFilterTag", testFilterTag),
|
||||
]
|
||||
}
|
||||
|
||||
extension FilterTests {
|
||||
static let __allTests = [
|
||||
("testDefaultFilter", testDefaultFilter),
|
||||
("testDynamicFilters", testDynamicFilters),
|
||||
("testFilterSuggestion", testFilterSuggestion),
|
||||
("testIndentContent", testIndentContent),
|
||||
("testIndentFirstLine", testIndentFirstLine),
|
||||
("testIndentNotEmptyLines", testIndentNotEmptyLines),
|
||||
("testIndentWithArbitraryCharacter", testIndentWithArbitraryCharacter),
|
||||
("testJoinFilter", testJoinFilter),
|
||||
("testRegistration", testRegistration),
|
||||
("testRegistrationOverrideDefault", testRegistrationOverrideDefault),
|
||||
("testRegistrationWithArguments", testRegistrationWithArguments),
|
||||
("testSplitFilter", testSplitFilter),
|
||||
("testStringFilters", testStringFilters),
|
||||
("testStringFiltersWithArrays", testStringFiltersWithArrays),
|
||||
]
|
||||
}
|
||||
|
||||
extension ForNodeTests {
|
||||
static let __allTests = [
|
||||
("testArrayOfTuples", testArrayOfTuples),
|
||||
("testForNode", testForNode),
|
||||
("testHandleInvalidInput", testHandleInvalidInput),
|
||||
("testIterateDictionary", testIterateDictionary),
|
||||
("testIterateRange", testIterateRange),
|
||||
("testIterateUsingMirroring", testIterateUsingMirroring),
|
||||
("testLoopMetadata", testLoopMetadata),
|
||||
("testWhereExpression", testWhereExpression),
|
||||
]
|
||||
}
|
||||
|
||||
extension IfNodeTests {
|
||||
static let __allTests = [
|
||||
("testEvaluatesNilAsFalse", testEvaluatesNilAsFalse),
|
||||
("testParseIf", testParseIf),
|
||||
("testParseIfnot", testParseIfnot),
|
||||
("testParseIfWithElif", testParseIfWithElif),
|
||||
("testParseIfWithElifWithoutElse", testParseIfWithElifWithoutElse),
|
||||
("testParseIfWithElse", testParseIfWithElse),
|
||||
("testParseMultipleElif", testParseMultipleElif),
|
||||
("testParsingErrors", testParsingErrors),
|
||||
("testRendering", testRendering),
|
||||
("testSupportsRangeVariables", testSupportsRangeVariables),
|
||||
("testSupportVariableFilters", testSupportVariableFilters),
|
||||
]
|
||||
}
|
||||
|
||||
extension IncludeTests {
|
||||
static let __allTests = [
|
||||
("testParsing", testParsing),
|
||||
("testRendering", testRendering),
|
||||
]
|
||||
}
|
||||
|
||||
extension InheritanceTests {
|
||||
static let __allTests = [
|
||||
("testInheritance", testInheritance),
|
||||
]
|
||||
}
|
||||
|
||||
extension LexerTests {
|
||||
static let __allTests = [
|
||||
("testComment", testComment),
|
||||
("testContentMixture", testContentMixture),
|
||||
("testEmptyVariable", testEmptyVariable),
|
||||
("testEscapeSequence", testEscapeSequence),
|
||||
("testNewlines", testNewlines),
|
||||
("testPerformance", testPerformance),
|
||||
("testText", testText),
|
||||
("testTokenizeIncorrectSyntaxWithoutCrashing", testTokenizeIncorrectSyntaxWithoutCrashing),
|
||||
("testTokenWithoutSpaces", testTokenWithoutSpaces),
|
||||
("testUnclosedBlock", testUnclosedBlock),
|
||||
("testUnclosedTag", testUnclosedTag),
|
||||
("testVariable", testVariable),
|
||||
("testVariablesWithoutBeingGreedy", testVariablesWithoutBeingGreedy),
|
||||
]
|
||||
}
|
||||
|
||||
extension NodeTests {
|
||||
static let __allTests = [
|
||||
("testRendering", testRendering),
|
||||
("testTextNode", testTextNode),
|
||||
("testVariableNode", testVariableNode),
|
||||
]
|
||||
}
|
||||
|
||||
extension NowNodeTests {
|
||||
static let __allTests = [
|
||||
("testParsing", testParsing),
|
||||
("testRendering", testRendering),
|
||||
]
|
||||
}
|
||||
|
||||
extension StencilTests {
|
||||
static let __allTests = [
|
||||
("testStencil", testStencil),
|
||||
]
|
||||
}
|
||||
|
||||
extension TemplateLoaderTests {
|
||||
static let __allTests = [
|
||||
("testDictionaryLoader", testDictionaryLoader),
|
||||
("testFileSystemLoader", testFileSystemLoader),
|
||||
]
|
||||
}
|
||||
|
||||
extension TemplateTests {
|
||||
static let __allTests = [
|
||||
("testTemplate", testTemplate),
|
||||
]
|
||||
}
|
||||
|
||||
extension TokenParserTests {
|
||||
static let __allTests = [
|
||||
("testTokenParser", testTokenParser),
|
||||
]
|
||||
}
|
||||
|
||||
extension TokenTests {
|
||||
static let __allTests = [
|
||||
("testToken", testToken),
|
||||
]
|
||||
}
|
||||
|
||||
extension VariableTests {
|
||||
static let __allTests = [
|
||||
("testArray", testArray),
|
||||
("testDictionary", testDictionary),
|
||||
("testKVO", testKVO),
|
||||
("testLiterals", testLiterals),
|
||||
("testMultipleSubscripting", testMultipleSubscripting),
|
||||
("testOptional", testOptional),
|
||||
("testRangeVariable", testRangeVariable),
|
||||
("testReflection", testReflection),
|
||||
("testSubscripting", testSubscripting),
|
||||
("testTuple", testTuple),
|
||||
("testVariable", testVariable),
|
||||
]
|
||||
}
|
||||
|
||||
#if !os(macOS)
|
||||
public func __allTests() -> [XCTestCaseEntry] {
|
||||
return [
|
||||
testCase(ContextTests.__allTests),
|
||||
testCase(EnvironmentBaseAndChildTemplateTests.__allTests),
|
||||
testCase(EnvironmentIncludeTemplateTests.__allTests),
|
||||
testCase(EnvironmentTests.__allTests),
|
||||
testCase(ExpressionsTests.__allTests),
|
||||
testCase(FilterTagTests.__allTests),
|
||||
testCase(FilterTests.__allTests),
|
||||
testCase(ForNodeTests.__allTests),
|
||||
testCase(IfNodeTests.__allTests),
|
||||
testCase(IncludeTests.__allTests),
|
||||
testCase(InheritanceTests.__allTests),
|
||||
testCase(LexerTests.__allTests),
|
||||
testCase(NodeTests.__allTests),
|
||||
testCase(NowNodeTests.__allTests),
|
||||
testCase(StencilTests.__allTests),
|
||||
testCase(TemplateLoaderTests.__allTests),
|
||||
testCase(TemplateTests.__allTests),
|
||||
testCase(TokenParserTests.__allTests),
|
||||
testCase(TokenTests.__allTests),
|
||||
testCase(VariableTests.__allTests),
|
||||
]
|
||||
}
|
||||
#endif
|
||||
@@ -10,7 +10,7 @@ else
|
||||
fi
|
||||
|
||||
# possible paths
|
||||
paths_sources="Sources"
|
||||
paths_sources="Sources/Stencil"
|
||||
paths_tests="Tests/StencilTests"
|
||||
|
||||
# load selected group
|
||||
|
||||
Reference in New Issue
Block a user