Merge pull request #323 from stencilproject/feature/drop-swift4
Drop Swift 4 support
This commit is contained in:
@@ -1,90 +1,112 @@
|
|||||||
swiftlint_version: 0.48.0
|
swiftlint_version: 0.48.0
|
||||||
|
|
||||||
disabled_rules:
|
|
||||||
# Remove this once we remove old swift support
|
|
||||||
- implicit_return
|
|
||||||
|
|
||||||
opt_in_rules:
|
opt_in_rules:
|
||||||
|
- accessibility_label_for_image
|
||||||
|
- anonymous_argument_in_multiline_closure
|
||||||
- anyobject_protocol
|
- anyobject_protocol
|
||||||
- array_init
|
- array_init
|
||||||
- attributes
|
- attributes
|
||||||
|
- balanced_xctest_lifecycle
|
||||||
- closure_body_length
|
- closure_body_length
|
||||||
- closure_end_indentation
|
- closure_end_indentation
|
||||||
- closure_spacing
|
- closure_spacing
|
||||||
- collection_alignment
|
- collection_alignment
|
||||||
|
- comment_spacing
|
||||||
|
- conditional_returns_on_newline
|
||||||
- contains_over_filter_count
|
- contains_over_filter_count
|
||||||
- contains_over_filter_is_empty
|
- contains_over_filter_is_empty
|
||||||
- contains_over_first_not_nil
|
- contains_over_first_not_nil
|
||||||
- contains_over_range_nil_comparison
|
- contains_over_range_nil_comparison
|
||||||
- convenience_type
|
- convenience_type
|
||||||
|
- discarded_notification_center_observer
|
||||||
|
- discouraged_assert
|
||||||
|
- discouraged_none_name
|
||||||
- discouraged_optional_boolean
|
- discouraged_optional_boolean
|
||||||
- discouraged_optional_collection
|
- discouraged_optional_collection
|
||||||
- duplicate_enum_cases
|
|
||||||
- duplicate_imports
|
|
||||||
- empty_collection_literal
|
- empty_collection_literal
|
||||||
- empty_count
|
- empty_count
|
||||||
- empty_string
|
- empty_string
|
||||||
|
- empty_xctest_method
|
||||||
|
- enum_case_associated_values_count
|
||||||
- fallthrough
|
- fallthrough
|
||||||
- fatal_error_message
|
- fatal_error_message
|
||||||
|
- file_header
|
||||||
- first_where
|
- first_where
|
||||||
- flatmap_over_map_reduce
|
- flatmap_over_map_reduce
|
||||||
- force_unwrapping
|
- force_unwrapping
|
||||||
|
- ibinspectable_in_extension
|
||||||
- identical_operands
|
- identical_operands
|
||||||
- inert_defer
|
- implicit_return
|
||||||
|
- implicitly_unwrapped_optional
|
||||||
|
- inclusive_language
|
||||||
|
- indentation_width
|
||||||
- joined_default_parameter
|
- joined_default_parameter
|
||||||
- last_where
|
- last_where
|
||||||
- legacy_hashing
|
- legacy_multiple
|
||||||
|
- legacy_objc_type
|
||||||
- legacy_random
|
- legacy_random
|
||||||
- literal_expression_end_indentation
|
- literal_expression_end_indentation
|
||||||
- lower_acl_than_parent
|
- lower_acl_than_parent
|
||||||
|
- missing_docs
|
||||||
- modifier_order
|
- modifier_order
|
||||||
- multiline_arguments
|
- multiline_arguments
|
||||||
|
- multiline_arguments_brackets
|
||||||
- multiline_function_chains
|
- multiline_function_chains
|
||||||
- multiline_literal_brackets
|
- multiline_literal_brackets
|
||||||
- multiline_parameters
|
- multiline_parameters
|
||||||
- multiline_parameters_brackets
|
- multiline_parameters_brackets
|
||||||
- nslocalizedstring_key
|
- nslocalizedstring_key
|
||||||
- nsobject_prefer_isequal
|
- nslocalizedstring_require_bundle
|
||||||
- number_separator
|
- number_separator
|
||||||
- object_literal
|
|
||||||
- operator_usage_whitespace
|
- operator_usage_whitespace
|
||||||
|
- optional_enum_case_matching
|
||||||
- overridden_super_call
|
- overridden_super_call
|
||||||
- override_in_extension
|
- override_in_extension
|
||||||
|
- prefer_self_in_static_references
|
||||||
- prefer_self_type_over_type_of_self
|
- prefer_self_type_over_type_of_self
|
||||||
|
- prefer_zero_over_explicit_init
|
||||||
|
- prefixed_toplevel_constant
|
||||||
- private_action
|
- private_action
|
||||||
- private_outlet
|
- private_outlet
|
||||||
|
- private_subject
|
||||||
- prohibited_super_call
|
- prohibited_super_call
|
||||||
- raw_value_for_camel_cased_codable_enum
|
- raw_value_for_camel_cased_codable_enum
|
||||||
- reduce_boolean
|
|
||||||
- reduce_into
|
- reduce_into
|
||||||
- redundant_nil_coalescing
|
- redundant_nil_coalescing
|
||||||
- redundant_objc_attribute
|
- redundant_type_annotation
|
||||||
|
- required_enum_case
|
||||||
|
- return_value_from_void_function
|
||||||
|
- single_test_class
|
||||||
- sorted_first_last
|
- sorted_first_last
|
||||||
- sorted_imports
|
- sorted_imports
|
||||||
- static_operator
|
- static_operator
|
||||||
- strong_iboutlet
|
- strong_iboutlet
|
||||||
|
- switch_case_on_newline
|
||||||
|
- test_case_accessibility
|
||||||
- toggle_bool
|
- toggle_bool
|
||||||
- trailing_closure
|
- trailing_closure
|
||||||
- unavailable_function
|
- unavailable_function
|
||||||
- unneeded_parentheses_in_closure_argument
|
- unneeded_parentheses_in_closure_argument
|
||||||
- unowned_variable_capture
|
- unowned_variable_capture
|
||||||
- unused_capture_list
|
- unused_closure_parameter
|
||||||
- unused_control_flow_label
|
|
||||||
- unused_declaration
|
|
||||||
- unused_setter_value
|
|
||||||
- vertical_parameter_alignment_on_call
|
- vertical_parameter_alignment_on_call
|
||||||
- vertical_whitespace_closing_braces
|
- vertical_whitespace_closing_braces
|
||||||
- vertical_whitespace_opening_braces
|
- vertical_whitespace_opening_braces
|
||||||
|
- void_function_in_ternary
|
||||||
|
- weak_delegate
|
||||||
- xct_specific_matcher
|
- xct_specific_matcher
|
||||||
- yoda_condition
|
- yoda_condition
|
||||||
# Enable this again once we remove old swift support
|
|
||||||
# - optional_enum_case_matching
|
|
||||||
# - legacy_multiple
|
|
||||||
|
|
||||||
# Rules customization
|
# Rules customization
|
||||||
closure_body_length:
|
closure_body_length:
|
||||||
warning: 25
|
warning: 25
|
||||||
|
|
||||||
|
conditional_returns_on_newline:
|
||||||
|
if_only: true
|
||||||
|
|
||||||
|
indentation_width:
|
||||||
|
indentation_width: 2
|
||||||
|
|
||||||
line_length:
|
line_length:
|
||||||
warning: 120
|
warning: 120
|
||||||
error: 200
|
error: 200
|
||||||
@@ -92,8 +114,3 @@ line_length:
|
|||||||
nesting:
|
nesting:
|
||||||
type_level:
|
type_level:
|
||||||
warning: 2
|
warning: 2
|
||||||
|
|
||||||
# Exclude generated files
|
|
||||||
excluded:
|
|
||||||
- .build
|
|
||||||
- Tests/StencilTests/XCTestManifests.swift
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
### Breaking
|
### 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
|
### Enhancements
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// swift-tools-version:4.2
|
// swift-tools-version:5.0
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
@@ -13,11 +13,11 @@ let package = Package(
|
|||||||
targets: [
|
targets: [
|
||||||
.target(name: "Stencil", dependencies: [
|
.target(name: "Stencil", dependencies: [
|
||||||
"PathKit"
|
"PathKit"
|
||||||
], path: "Sources"),
|
]),
|
||||||
.testTarget(name: "StencilTests", dependencies: [
|
.testTarget(name: "StencilTests", dependencies: [
|
||||||
"Stencil",
|
"Stencil",
|
||||||
"Spectre"
|
"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
|
# Stencil
|
||||||
|
|
||||||
[](https://travis-ci.org/stencilproject/Stencil)
|
|
||||||
|
|
||||||
Stencil is a simple and powerful template language for Swift. It provides a
|
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
|
syntax similar to Django and Mustache. If you're familiar with these, you will
|
||||||
feel right at home with Stencil.
|
feel right at home with Stencil.
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
public class Context {
|
public class Context {
|
||||||
var dictionaries: [[String: Any?]]
|
var dictionaries: [[String: Any?]]
|
||||||
|
|
||||||
|
/// The context's environment, such as registered extensions, classes, …
|
||||||
public let environment: Environment
|
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) {
|
public init(dictionary: [String: Any] = [:], environment: Environment? = nil) {
|
||||||
if !dictionary.isEmpty {
|
if !dictionary.isEmpty {
|
||||||
dictionaries = [dictionary]
|
dictionaries = [dictionary]
|
||||||
@@ -14,6 +20,7 @@ public class Context {
|
|||||||
self.environment = environment ?? Environment()
|
self.environment = environment ?? Environment()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access variables in this context by name
|
||||||
public subscript(key: String) -> Any? {
|
public subscript(key: String) -> Any? {
|
||||||
/// Retrieves a variable's value, starting at the current context and going upwards
|
/// Retrieves a variable's value, starting at the current context and going upwards
|
||||||
get {
|
get {
|
||||||
@@ -36,22 +43,35 @@ public class Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push a new level into the Context
|
/// Push a new level into the Context
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - dictionary: The new level data
|
||||||
fileprivate func push(_ dictionary: [String: Any] = [:]) {
|
fileprivate func push(_ dictionary: [String: Any] = [:]) {
|
||||||
dictionaries.append(dictionary)
|
dictionaries.append(dictionary)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop the last level off of the Context
|
/// Pop the last level off of the Context
|
||||||
|
///
|
||||||
|
/// - returns: The popped level
|
||||||
fileprivate func pop() -> [String: Any?]? {
|
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
|
/// 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 {
|
public func push<Result>(dictionary: [String: Any] = [:], closure: (() throws -> Result)) rethrows -> Result {
|
||||||
push(dictionary)
|
push(dictionary)
|
||||||
defer { _ = pop() }
|
defer { _ = pop() }
|
||||||
return try closure()
|
return try closure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flatten all levels of context data into 1, merging duplicate variables
|
||||||
|
///
|
||||||
|
/// - returns: All collected variables
|
||||||
public func flatten() -> [String: Any] {
|
public func flatten() -> [String: Any] {
|
||||||
var accumulator: [String: Any] = [:]
|
var accumulator: [String: Any] = [:]
|
||||||
|
|
||||||
@@ -6,10 +6,13 @@ public protocol DynamicMemberLookup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public extension DynamicMemberLookup where Self: RawRepresentable {
|
public extension DynamicMemberLookup where Self: RawRepresentable {
|
||||||
|
/// Get a value for a given `String` key
|
||||||
subscript(dynamicMember member: String) -> Any? {
|
subscript(dynamicMember member: String) -> Any? {
|
||||||
switch member {
|
switch member {
|
||||||
case "rawValue": return rawValue
|
case "rawValue":
|
||||||
default: return nil
|
return rawValue
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,18 @@
|
|||||||
|
/// Container for environment data, such as registered extensions
|
||||||
public struct Environment {
|
public struct Environment {
|
||||||
|
/// The class for loading new templates
|
||||||
public let templateClass: Template.Type
|
public let templateClass: Template.Type
|
||||||
|
/// List of registered extensions
|
||||||
public var extensions: [Extension]
|
public var extensions: [Extension]
|
||||||
|
/// Mechanism for loading new files
|
||||||
public var loader: Loader?
|
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(
|
public init(
|
||||||
loader: Loader? = nil,
|
loader: Loader? = nil,
|
||||||
extensions: [Extension] = [],
|
extensions: [Extension] = [],
|
||||||
@@ -13,6 +23,11 @@ public struct Environment {
|
|||||||
self.extensions = extensions + [DefaultExtension()]
|
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 {
|
public func loadTemplate(name: String) throws -> Template {
|
||||||
if let loader = loader {
|
if let loader = loader {
|
||||||
return try loader.loadTemplate(name: name, environment: self)
|
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 {
|
public func loadTemplate(names: [String]) throws -> Template {
|
||||||
if let loader = loader {
|
if let loader = loader {
|
||||||
return try loader.loadTemplate(names: names, environment: self)
|
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 {
|
public func renderTemplate(name: String, context: [String: Any] = [:]) throws -> String {
|
||||||
let template = try loadTemplate(name: name)
|
let template = try loadTemplate(name: name)
|
||||||
return try render(template: template, context: context)
|
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 {
|
public func renderTemplate(string: String, context: [String: Any] = [:]) throws -> String {
|
||||||
let template = templateClass.init(templateString: string, environment: self)
|
let template = templateClass.init(templateString: string, environment: self)
|
||||||
return try render(template: template, context: context)
|
return try render(template: template, context: context)
|
||||||
@@ -20,12 +20,12 @@ public class TemplateDoesNotExist: Error, CustomStringConvertible {
|
|||||||
|
|
||||||
public struct TemplateSyntaxError: Error, Equatable, CustomStringConvertible {
|
public struct TemplateSyntaxError: Error, Equatable, CustomStringConvertible {
|
||||||
public let reason: String
|
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 token: Token?
|
||||||
public internal(set) var stackTrace: [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] {
|
var allTokens: [Token] {
|
||||||
return stackTrace + (token.map { [$0] } ?? [])
|
stackTrace + (token.map { [$0] } ?? [])
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(reason: String, token: Token? = nil, stackTrace: [Token] = []) {
|
public init(reason: String, token: Token? = nil, stackTrace: [Token] = []) {
|
||||||
@@ -18,11 +18,11 @@ final class StaticExpression: Expression, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
func evaluate(context: Context) throws -> Bool {
|
||||||
return value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
return "\(value)"
|
"\(value)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ final class VariableExpression: Expression, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
return "(variable: \(variable))"
|
"(variable: \(variable))"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves a variable in the given context as boolean
|
/// Resolves a variable in the given context as boolean
|
||||||
@@ -60,7 +60,7 @@ final class VariableExpression: Expression, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
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 {
|
var description: String {
|
||||||
return "not \(expression)"
|
"not \(expression)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
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 {
|
var description: String {
|
||||||
return "(\(lhs) in \(rhs))"
|
"(\(lhs) in \(rhs))"
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
func evaluate(context: Context) throws -> Bool {
|
||||||
@@ -125,7 +125,7 @@ final class OrExpression: Expression, InfixOperator, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
return "(\(lhs) or \(rhs))"
|
"(\(lhs) or \(rhs))"
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
func evaluate(context: Context) throws -> Bool {
|
||||||
@@ -148,7 +148,7 @@ final class AndExpression: Expression, InfixOperator, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
return "(\(lhs) and \(rhs))"
|
"(\(lhs) and \(rhs))"
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
func evaluate(context: Context) throws -> Bool {
|
||||||
@@ -171,7 +171,7 @@ class EqualityExpression: Expression, InfixOperator, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
return "(\(lhs) == \(rhs))"
|
"(\(lhs) == \(rhs))"
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
func evaluate(context: Context) throws -> Bool {
|
||||||
@@ -206,7 +206,7 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
return "(\(lhs) \(symbol) \(rhs))"
|
"(\(lhs) \(symbol) \(rhs))"
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
func evaluate(context: Context) throws -> Bool {
|
||||||
@@ -225,61 +225,61 @@ class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var symbol: String {
|
var symbol: String {
|
||||||
return ""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
func compare(lhs: Number, rhs: Number) -> Bool {
|
func compare(lhs: Number, rhs: Number) -> Bool {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoreThanExpression: NumericExpression {
|
class MoreThanExpression: NumericExpression {
|
||||||
override var symbol: String {
|
override var symbol: String {
|
||||||
return ">"
|
">"
|
||||||
}
|
}
|
||||||
|
|
||||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||||
return lhs > rhs
|
lhs > rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoreThanEqualExpression: NumericExpression {
|
class MoreThanEqualExpression: NumericExpression {
|
||||||
override var symbol: String {
|
override var symbol: String {
|
||||||
return ">="
|
">="
|
||||||
}
|
}
|
||||||
|
|
||||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||||
return lhs >= rhs
|
lhs >= rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LessThanExpression: NumericExpression {
|
class LessThanExpression: NumericExpression {
|
||||||
override var symbol: String {
|
override var symbol: String {
|
||||||
return "<"
|
"<"
|
||||||
}
|
}
|
||||||
|
|
||||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||||
return lhs < rhs
|
lhs < rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LessThanEqualExpression: NumericExpression {
|
class LessThanEqualExpression: NumericExpression {
|
||||||
override var symbol: String {
|
override var symbol: String {
|
||||||
return "<="
|
"<="
|
||||||
}
|
}
|
||||||
|
|
||||||
override func compare(lhs: Number, rhs: Number) -> Bool {
|
override func compare(lhs: Number, rhs: Number) -> Bool {
|
||||||
return lhs <= rhs
|
lhs <= rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InequalityExpression: EqualityExpression {
|
class InequalityExpression: EqualityExpression {
|
||||||
override var description: String {
|
override var description: String {
|
||||||
return "(\(lhs) != \(rhs))"
|
"(\(lhs) != \(rhs))"
|
||||||
}
|
}
|
||||||
|
|
||||||
override func evaluate(context: Context) throws -> Bool {
|
override func evaluate(context: Context) throws -> Bool {
|
||||||
return try !super.evaluate(context: context)
|
try !super.evaluate(context: context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
/// Container for registered tags and filters
|
||||||
open class Extension {
|
open class Extension {
|
||||||
typealias TagParser = (TokenParser, Token) throws -> NodeType
|
typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||||
var tags = [String: TagParser]()
|
|
||||||
|
|
||||||
|
var tags = [String: TagParser]()
|
||||||
var filters = [String: Filter]()
|
var filters = [String: Filter]()
|
||||||
|
|
||||||
|
/// Simple initializer
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,11 +22,11 @@ open class Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Registers boolean filter with it's negative counterpart
|
/// 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?) {
|
public func registerFilter(name: String, negativeFilterName: String, filter: @escaping (Any?) throws -> Bool?) {
|
||||||
|
// swiftlint:disable:previous discouraged_optional_boolean
|
||||||
filters[name] = .simple(filter)
|
filters[name] = .simple(filter)
|
||||||
filters[negativeFilterName] = .simple {
|
filters[negativeFilterName] = .simple { value in
|
||||||
guard let result = try filter($0) else { return nil }
|
guard let result = try filter(value) else { return nil }
|
||||||
return !result
|
return !result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,9 +74,11 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
|||||||
var indentWidth = 4
|
var indentWidth = 4
|
||||||
if !arguments.isEmpty {
|
if !arguments.isEmpty {
|
||||||
guard let value = arguments[0] as? Int else {
|
guard let value = arguments[0] as? Int else {
|
||||||
throw TemplateSyntaxError("""
|
throw TemplateSyntaxError(
|
||||||
|
"""
|
||||||
'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))
|
'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
indentWidth = value
|
indentWidth = value
|
||||||
}
|
}
|
||||||
@@ -84,9 +86,11 @@ func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
|
|||||||
var indentationChar = " "
|
var indentationChar = " "
|
||||||
if arguments.count > 1 {
|
if arguments.count > 1 {
|
||||||
guard let value = arguments[1] as? String else {
|
guard let value = arguments[1] as? String else {
|
||||||
throw TemplateSyntaxError("""
|
throw TemplateSyntaxError(
|
||||||
|
"""
|
||||||
'indent' filter indentation argument must be a String (\(String(describing: arguments[1]))
|
'indent' filter indentation argument must be a String (\(String(describing: arguments[1]))
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
indentationChar = value
|
indentationChar = value
|
||||||
}
|
}
|
||||||
@@ -12,11 +12,11 @@ class ForNode: NodeType {
|
|||||||
let components = token.components
|
let components = token.components
|
||||||
|
|
||||||
func hasToken(_ token: String, at index: Int) -> Bool {
|
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 {
|
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 {
|
guard hasToken("in", at: 2) && endsOrHasToken("where", at: 4) else {
|
||||||
@@ -154,9 +154,9 @@ class ForNode: NodeType {
|
|||||||
} else if let resolved = resolved {
|
} else if let resolved = resolved {
|
||||||
let mirror = Mirror(reflecting: resolved)
|
let mirror = Mirror(reflecting: resolved)
|
||||||
switch mirror.displayStyle {
|
switch mirror.displayStyle {
|
||||||
case .struct?, .tuple?:
|
case .struct, .tuple:
|
||||||
values = Array(mirror.children)
|
values = Array(mirror.children)
|
||||||
case .class?:
|
case .class:
|
||||||
var children = Array(mirror.children)
|
var children = Array(mirror.children)
|
||||||
var currentMirror: Mirror? = mirror
|
var currentMirror: Mirror? = mirror
|
||||||
while let superclassMirror = currentMirror?.superclassMirror {
|
while let superclassMirror = currentMirror?.superclassMirror {
|
||||||
@@ -10,9 +10,8 @@ enum Operator {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let operators: [Operator] = [
|
static let all: [Operator] = [
|
||||||
.infix("in", 5, InExpression.self),
|
.infix("in", 5, InExpression.self),
|
||||||
.infix("or", 6, OrExpression.self),
|
.infix("or", 6, OrExpression.self),
|
||||||
.infix("and", 7, AndExpression.self),
|
.infix("and", 7, AndExpression.self),
|
||||||
@@ -24,9 +23,10 @@ let operators: [Operator] = [
|
|||||||
.infix("<", 10, LessThanExpression.self),
|
.infix("<", 10, LessThanExpression.self),
|
||||||
.infix("<=", 10, LessThanEqualExpression.self)
|
.infix("<=", 10, LessThanEqualExpression.self)
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
func findOperator(name: String) -> Operator? {
|
func findOperator(name: String) -> Operator? {
|
||||||
for `operator` in operators where `operator`.name == name {
|
for `operator` in Operator.all where `operator`.name == name {
|
||||||
return `operator`
|
return `operator`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ final class IfExpressionParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func parser(components: [String], environment: Environment, token: Token) throws -> 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 {
|
private init(components: ArraySlice<String>, environment: Environment, token: Token) throws {
|
||||||
@@ -117,7 +117,7 @@ final class IfExpressionParser {
|
|||||||
|
|
||||||
if component == "(" {
|
if component == "(" {
|
||||||
bracketsBalance += 1
|
bracketsBalance += 1
|
||||||
let (expression, parsedCount) = try IfExpressionParser.subExpression(
|
let (expression, parsedCount) = try Self.subExpression(
|
||||||
from: components.suffix(from: index + 1),
|
from: components.suffix(from: index + 1),
|
||||||
environment: environment,
|
environment: environment,
|
||||||
token: token
|
token: token
|
||||||
@@ -152,10 +152,10 @@ final class IfExpressionParser {
|
|||||||
token: Token
|
token: Token
|
||||||
) throws -> (Expression, Int) {
|
) throws -> (Expression, Int) {
|
||||||
var bracketsBalance = 1
|
var bracketsBalance = 1
|
||||||
let subComponents = components.prefix {
|
let subComponents = components.prefix { component in
|
||||||
if $0 == "(" {
|
if component == "(" {
|
||||||
bracketsBalance += 1
|
bracketsBalance += 1
|
||||||
} else if $0 == ")" {
|
} else if component == ")" {
|
||||||
bracketsBalance -= 1
|
bracketsBalance -= 1
|
||||||
}
|
}
|
||||||
return bracketsBalance != 0
|
return bracketsBalance != 0
|
||||||
@@ -220,7 +220,7 @@ final class IfCondition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
return try context.push {
|
try context.push {
|
||||||
try renderNodes(nodes, context)
|
try renderNodes(nodes, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,11 +9,13 @@ class IncludeNode: NodeType {
|
|||||||
let bits = token.components
|
let bits = token.components
|
||||||
|
|
||||||
guard bits.count == 2 || bits.count == 3 else {
|
guard bits.count == 2 || bits.count == 3 else {
|
||||||
throw TemplateSyntaxError("""
|
throw TemplateSyntaxError(
|
||||||
|
"""
|
||||||
'include' tag requires one argument, the template file to be included. \
|
'include' tag requires one argument, the template file to be included. \
|
||||||
A second optional argument can be used to specify the context that will \
|
A second optional argument can be used to specify the context that will \
|
||||||
be passed to the included file
|
be passed to the included file
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return IncludeNode(templateName: Variable(bits[1]), includeContext: bits.count == 3 ? bits[2] : nil, token: token)
|
return IncludeNode(templateName: Variable(bits[1]), includeContext: bits.count == 3 ? bits[2] : nil, token: token)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
class BlockContext {
|
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
|
// contains mapping of block names to their nodes and templates where they are defined
|
||||||
var blocks: [String: [BlockNode]]
|
var blocks: [String: [BlockNode]]
|
||||||
@@ -23,10 +23,10 @@ struct Lexer {
|
|||||||
self.templateName = templateName
|
self.templateName = templateName
|
||||||
self.templateString = templateString
|
self.templateString = templateString
|
||||||
|
|
||||||
self.lines = templateString.components(separatedBy: .newlines).enumerated().compactMap {
|
self.lines = zip(1..., templateString.components(separatedBy: .newlines)).compactMap { index, line in
|
||||||
guard !$0.element.isEmpty,
|
guard !line.isEmpty,
|
||||||
let range = templateString.range(of: $0.element) else { return nil }
|
let range = templateString.range(of: line) else { return nil }
|
||||||
return (content: $0.element, number: UInt($0.offset + 1), range)
|
return (content: line, number: UInt(index), range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +79,12 @@ struct Lexer {
|
|||||||
|
|
||||||
let scanner = Scanner(templateString)
|
let scanner = Scanner(templateString)
|
||||||
while !scanner.isEmpty {
|
while !scanner.isEmpty {
|
||||||
if let (char, text) = scanner.scanForTokenStart(Lexer.tokenChars) {
|
if let (char, text) = scanner.scanForTokenStart(Self.tokenChars) {
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
tokens.append(createToken(string: text, at: scanner.range))
|
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)
|
let result = scanner.scanForTokenEnd(end)
|
||||||
tokens.append(createToken(string: result, at: scanner.range))
|
tokens.append(createToken(string: result, at: scanner.range))
|
||||||
} else {
|
} else {
|
||||||
@@ -127,7 +127,7 @@ class Scanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isEmpty: Bool {
|
var isEmpty: Bool {
|
||||||
return content.isEmpty
|
content.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scans for the end of a token, with a specific ending character. If we're
|
/// 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 {
|
func scanForTokenEnd(_ tokenChar: Unicode.Scalar) -> String {
|
||||||
var foundChar = false
|
var foundChar = false
|
||||||
|
|
||||||
for (index, char) in content.unicodeScalars.enumerated() {
|
for (index, char) in zip(0..., content.unicodeScalars) {
|
||||||
if foundChar && char == Scanner.tokenEndDelimiter {
|
if foundChar && char == Self.tokenEndDelimiter {
|
||||||
let result = String(content.unicodeScalars.prefix(index + 1))
|
let result = String(content.unicodeScalars.prefix(index + 1))
|
||||||
content = String(content.unicodeScalars.dropFirst(index + 1))
|
content = String(content.unicodeScalars.dropFirst(index + 1))
|
||||||
range = range.upperBound..<originalContent.unicodeScalars.index(range.upperBound, offsetBy: index + 1)
|
range = range.upperBound..<originalContent.unicodeScalars.index(range.upperBound, offsetBy: index + 1)
|
||||||
@@ -178,14 +178,14 @@ class Scanner {
|
|||||||
var foundBrace = false
|
var foundBrace = false
|
||||||
|
|
||||||
range = range.upperBound..<range.upperBound
|
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) {
|
if foundBrace && tokenChars.contains(char) {
|
||||||
let result = String(content.unicodeScalars.prefix(index - 1))
|
let result = String(content.unicodeScalars.prefix(index - 1))
|
||||||
content = String(content.unicodeScalars.dropFirst(index - 1))
|
content = String(content.unicodeScalars.dropFirst(index - 1))
|
||||||
range = range.upperBound..<originalContent.unicodeScalars.index(range.upperBound, offsetBy: index - 1)
|
range = range.upperBound..<originalContent.unicodeScalars.index(range.upperBound, offsetBy: index - 1)
|
||||||
return (char, result)
|
return (char, result)
|
||||||
} else {
|
} 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)
|
public typealias ContentLocation = (content: String, lineNumber: UInt, lineOffset: Int)
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import PathKit
|
import PathKit
|
||||||
|
|
||||||
|
/// Type used for loading a template
|
||||||
public protocol Loader {
|
public protocol Loader {
|
||||||
|
/// Load a template with the given name
|
||||||
func loadTemplate(name: String, environment: Environment) throws -> Template
|
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
|
func loadTemplate(names: [String], environment: Environment) throws -> Template
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Loader {
|
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 {
|
public func loadTemplate(names: [String], environment: Environment) throws -> Template {
|
||||||
for name in names {
|
for name in names {
|
||||||
do {
|
do {
|
||||||
@@ -31,13 +35,13 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public init(bundle: [Bundle]) {
|
public init(bundle: [Bundle]) {
|
||||||
self.paths = bundle.map {
|
self.paths = bundle.map { bundle in
|
||||||
Path($0.bundlePath)
|
Path(bundle.bundlePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "FileSystemLoader(\(paths))"
|
"FileSystemLoader(\(paths))"
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadTemplate(name: String, environment: Environment) throws -> Template {
|
public func loadTemplate(name: String, environment: Environment) throws -> Template {
|
||||||
@@ -119,6 +123,6 @@ class SuspiciousFileOperation: Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
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
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents a parsed node
|
||||||
public protocol NodeType {
|
public protocol NodeType {
|
||||||
/// Render the node in the given context
|
/// Render the node in the given context
|
||||||
func render(_ context: Context) throws -> String
|
func render(_ context: Context) throws -> String
|
||||||
@@ -10,17 +11,18 @@ public protocol NodeType {
|
|||||||
|
|
||||||
/// Render the collection of nodes in the given context
|
/// Render the collection of nodes in the given context
|
||||||
public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String {
|
public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String {
|
||||||
return try nodes
|
try nodes
|
||||||
.map {
|
.map { node in
|
||||||
do {
|
do {
|
||||||
return try $0.render(context)
|
return try node.render(context)
|
||||||
} catch {
|
} catch {
|
||||||
throw error.withToken($0.token)
|
throw error.withToken(node.token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.joined()
|
.joined()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple node, used for triggering a closure during rendering
|
||||||
public class SimpleNode: NodeType {
|
public class SimpleNode: NodeType {
|
||||||
public let handler: (Context) throws -> String
|
public let handler: (Context) throws -> String
|
||||||
public let token: Token?
|
public let token: Token?
|
||||||
@@ -31,10 +33,11 @@ public class SimpleNode: NodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func render(_ context: Context) throws -> String {
|
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 class TextNode: NodeType {
|
||||||
public let text: String
|
public let text: String
|
||||||
public let token: Token?
|
public let token: Token?
|
||||||
@@ -45,14 +48,17 @@ public class TextNode: NodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func render(_ context: Context) throws -> String {
|
public func render(_ context: Context) throws -> String {
|
||||||
return self.text
|
self.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Representing something that can be resolved in a context
|
||||||
public protocol Resolvable {
|
public protocol Resolvable {
|
||||||
|
/// Try to resolve this with the given context
|
||||||
func resolve(_ context: Context) throws -> Any?
|
func resolve(_ context: Context) throws -> Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a variable, renders the variable, may have conditional expressions.
|
||||||
public class VariableNode: NodeType {
|
public class VariableNode: NodeType {
|
||||||
public let variable: Resolvable
|
public let variable: Resolvable
|
||||||
public var token: Token?
|
public var token: Token?
|
||||||
@@ -63,7 +69,7 @@ public class VariableNode: NodeType {
|
|||||||
let components = token.components
|
let components = token.components
|
||||||
|
|
||||||
func hasToken(_ token: String, at index: Int) -> Bool {
|
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?
|
let condition: Expression?
|
||||||
@@ -137,7 +143,7 @@ func stringify(_ result: Any?) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func unwrap(_ array: [Any?]) -> [Any] {
|
func unwrap(_ array: [Any?]) -> [Any] {
|
||||||
return array.map { (item: Any?) -> Any in
|
array.map { (item: Any?) -> Any in
|
||||||
if let item = item {
|
if let item = item {
|
||||||
if let items = item as? [Any?] {
|
if let items = item as? [Any?] {
|
||||||
return unwrap(items)
|
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) {
|
public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
|
||||||
return { _, token in
|
{ _, token in
|
||||||
if let name = token.components.first {
|
if let name = token.components.first {
|
||||||
for tag in tags where name == tag {
|
for tag in tags where name == tag {
|
||||||
return true
|
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
|
/// A class for parsing an array of tokens and converts them into a collection of Node's
|
||||||
public class TokenParser {
|
public class TokenParser {
|
||||||
|
/// Parser for finding a kind of node
|
||||||
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||||
|
|
||||||
fileprivate var tokens: [Token]
|
fileprivate var tokens: [Token]
|
||||||
fileprivate let environment: Environment
|
fileprivate let environment: Environment
|
||||||
|
|
||||||
|
/// Simple initializer
|
||||||
public init(tokens: [Token], environment: Environment) {
|
public init(tokens: [Token], environment: Environment) {
|
||||||
self.tokens = tokens
|
self.tokens = tokens
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
@@ -24,9 +28,11 @@ public class TokenParser {
|
|||||||
|
|
||||||
/// Parse the given tokens into nodes
|
/// Parse the given tokens into nodes
|
||||||
public func parse() throws -> [NodeType] {
|
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] {
|
public func parse(_ parseUntil: ((_ parser: TokenParser, _ token: Token) -> (Bool))?) throws -> [NodeType] {
|
||||||
var nodes = [NodeType]()
|
var nodes = [NodeType]()
|
||||||
|
|
||||||
@@ -61,6 +67,7 @@ public class TokenParser {
|
|||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pop the next token (returning it)
|
||||||
public func nextToken() -> Token? {
|
public func nextToken() -> Token? {
|
||||||
if !tokens.isEmpty {
|
if !tokens.isEmpty {
|
||||||
return tokens.remove(at: 0)
|
return tokens.remove(at: 0)
|
||||||
@@ -69,23 +76,24 @@ public class TokenParser {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a token
|
||||||
public func prependToken(_ token: Token) {
|
public func prependToken(_ token: Token) {
|
||||||
tokens.insert(token, at: 0)
|
tokens.insert(token, at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create filter expression from a string contained in provided token
|
/// Create filter expression from a string contained in provided token
|
||||||
public func compileFilter(_ filterToken: String, containedIn token: Token) throws -> Resolvable {
|
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
|
/// Create boolean expression from components contained in provided token
|
||||||
public func compileExpression(components: [String], token: Token) throws -> Expression {
|
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
|
/// 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 {
|
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 {
|
if suggestedFilters.isEmpty {
|
||||||
throw TemplateSyntaxError("Unknown filter '\(name)'.")
|
throw TemplateSyntaxError("Unknown filter '\(name)'.")
|
||||||
} else {
|
} else {
|
||||||
throw TemplateSyntaxError("""
|
throw TemplateSyntaxError(
|
||||||
|
"""
|
||||||
Unknown filter '\(name)'. \
|
Unknown filter '\(name)'. \
|
||||||
Found similar filters: \(suggestedFilters.map { "'\($0)'" }.joined(separator: ", ")).
|
Found similar filters: \(suggestedFilters.map { "'\($0)'" }.joined(separator: ", ")).
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +144,7 @@ extension Environment {
|
|||||||
|
|
||||||
/// Create filter expression from a string
|
/// Create filter expression from a string
|
||||||
public func compileFilter(_ token: String) throws -> Resolvable {
|
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
|
/// 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
|
/// Create resolvable (i.e. range variable or filter expression) from a string
|
||||||
public func compileResolvable(_ token: String) throws -> Resolvable {
|
public func compileResolvable(_ token: String) throws -> Resolvable {
|
||||||
return try RangeVariable(token, environment: self)
|
try RangeVariable(token, environment: self)
|
||||||
?? compileFilter(token)
|
?? compileFilter(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create resolvable (i.e. range variable or filter expression) from a string contained in provided 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 {
|
public func compileResolvable(_ token: String, containedIn containingToken: Token) throws -> Resolvable {
|
||||||
return try RangeVariable(token, environment: self, containedIn: containingToken)
|
try RangeVariable(token, environment: self, containedIn: containingToken)
|
||||||
?? compileFilter(token, containedIn: containingToken)
|
?? compileFilter(token, containedIn: containingToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create boolean expression from components contained in provided token
|
/// Create boolean expression from components contained in provided token
|
||||||
public func compileExpression(components: [String], containedIn token: Token) throws -> Expression {
|
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
|
// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
|
||||||
extension String {
|
extension String {
|
||||||
subscript(_ index: Int) -> Character {
|
subscript(_ index: Int) -> Character {
|
||||||
return self[self.index(self.startIndex, offsetBy: index)]
|
self[self.index(self.startIndex, offsetBy: index)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func levenshteinDistance(_ target: String) -> Int {
|
func levenshteinDistance(_ target: String) -> Int {
|
||||||
@@ -2,6 +2,7 @@ import Foundation
|
|||||||
import PathKit
|
import PathKit
|
||||||
|
|
||||||
#if os(Linux)
|
#if os(Linux)
|
||||||
|
// swiftlint:disable:next prefixed_toplevel_constant
|
||||||
let NSFileNoSuchFileError = 4
|
let NSFileNoSuchFileError = 4
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -77,6 +78,6 @@ open class Template: ExpressibleByStringLiteral {
|
|||||||
// swiftlint:disable discouraged_optional_collection
|
// swiftlint:disable discouraged_optional_collection
|
||||||
/// Render the given template
|
/// Render the given template
|
||||||
open func render(_ dictionary: [String: Any]? = nil) throws -> String {
|
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 character == separate {
|
||||||
if separate != separator {
|
if separate != separator {
|
||||||
word.append(separate)
|
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)
|
appendWord(word, to: &components)
|
||||||
word = ""
|
word = ""
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ public struct SourceMap: Equatable {
|
|||||||
static let unknown = SourceMap()
|
static let unknown = SourceMap()
|
||||||
|
|
||||||
public static func == (lhs: SourceMap, rhs: SourceMap) -> Bool {
|
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.
|
/// A token representing a piece of text.
|
||||||
public static func text(value: String, at sourceMap: SourceMap) -> Token {
|
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.
|
/// A token representing a variable.
|
||||||
public static func variable(value: String, at sourceMap: SourceMap) -> Token {
|
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.
|
/// A token representing a comment.
|
||||||
public static func comment(value: String, at sourceMap: SourceMap) -> Token {
|
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.
|
/// A token representing a template block.
|
||||||
public static func block(value: String, at sourceMap: SourceMap) -> Token {
|
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 {
|
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)]
|
let filterBits = bits[bits.indices.suffix(from: 1)]
|
||||||
|
|
||||||
do {
|
do {
|
||||||
filters = try filterBits.map {
|
filters = try filterBits.map { bit in
|
||||||
let (name, arguments) = parseFilterComponents(token: $0)
|
let (name, arguments) = parseFilterComponents(token: bit)
|
||||||
let filter = try environment.findFilter(name)
|
let filter = try environment.findFilter(name)
|
||||||
return (filter, arguments)
|
return (filter, arguments)
|
||||||
}
|
}
|
||||||
@@ -208,13 +208,14 @@ protocol Normalizable {
|
|||||||
|
|
||||||
extension Array: Normalizable {
|
extension Array: Normalizable {
|
||||||
func normalize() -> Any? {
|
func normalize() -> Any? {
|
||||||
return map { $0 as Any }
|
map { $0 as Any }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable:next legacy_objc_type
|
||||||
extension NSArray: Normalizable {
|
extension NSArray: Normalizable {
|
||||||
func normalize() -> Any? {
|
func normalize() -> Any? {
|
||||||
return map { $0 as Any }
|
map { $0 as Any }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,8 +274,10 @@ protocol AnyOptional {
|
|||||||
extension Optional: AnyOptional {
|
extension Optional: AnyOptional {
|
||||||
var wrapped: Any? {
|
var wrapped: Any? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .some(value): return value
|
case let .some(value):
|
||||||
case .none: return nil
|
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"
|
"tag": "0.14.2"
|
||||||
},
|
},
|
||||||
"source_files": [
|
"source_files": [
|
||||||
"Sources/*.swift"
|
"Sources/Stencil/*.swift"
|
||||||
],
|
],
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"ios": "8.0",
|
"ios": "8.0",
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
},
|
},
|
||||||
"cocoapods_version": ">= 1.7.0",
|
"cocoapods_version": ">= 1.7.0",
|
||||||
"swift_versions": [
|
"swift_versions": [
|
||||||
"4.2",
|
|
||||||
"5.0"
|
"5.0"
|
||||||
],
|
],
|
||||||
"requires_arc": true,
|
"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 {
|
final class ContextTests: XCTestCase {
|
||||||
func testContextSubscripting() {
|
func testContextSubscripting() {
|
||||||
describe("Context Subscripting") {
|
describe("Context Subscripting") { test in
|
||||||
var context = Context()
|
var context = Context()
|
||||||
$0.before {
|
test.before {
|
||||||
context = Context(dictionary: ["name": "Kyle"])
|
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"
|
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"
|
context["name"] = "Katie"
|
||||||
|
|
||||||
try expect(context["name"] as? String) == "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
|
context["name"] = nil
|
||||||
|
|
||||||
try expect(context["name"]).to.beNil()
|
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 context.push {
|
||||||
try expect(context["name"] as? String) == "Kyle"
|
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 {
|
try context.push {
|
||||||
context["name"] = "Katie"
|
context["name"] = "Katie"
|
||||||
try expect(context["name"] as? String) == "Katie"
|
try expect(context["name"] as? String) == "Katie"
|
||||||
@@ -42,13 +42,13 @@ final class ContextTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testContextRestoration() {
|
func testContextRestoration() {
|
||||||
describe("Context Restoration") {
|
describe("Context Restoration") { test in
|
||||||
var context = Context()
|
var context = Context()
|
||||||
$0.before {
|
test.before {
|
||||||
context = Context(dictionary: ["name": "Kyle"])
|
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.push {
|
||||||
context["name"] = "Katie"
|
context["name"] = "Katie"
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ final class ContextTests: XCTestCase {
|
|||||||
try expect(context["name"] as? String) == "Kyle"
|
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 {
|
try context.push {
|
||||||
context["name"] = nil
|
context["name"] = nil
|
||||||
try expect(context["name"]).to.beNil()
|
try expect(context["name"]).to.beNil()
|
||||||
@@ -65,7 +65,7 @@ final class ContextTests: XCTestCase {
|
|||||||
try expect(context["name"] as? String) == "Kyle"
|
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
|
var didRun = false
|
||||||
|
|
||||||
try context.push(dictionary: ["name": "Katie"]) {
|
try context.push(dictionary: ["name": "Katie"]) {
|
||||||
@@ -77,7 +77,7 @@ final class ContextTests: XCTestCase {
|
|||||||
try expect(context["name"] as? String) == "Kyle"
|
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"]) {
|
try context.push(dictionary: ["test": "abc"]) {
|
||||||
let flattened = context.flatten()
|
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
|
import XCTest
|
||||||
|
|
||||||
final class EnvironmentTests: XCTestCase {
|
final class EnvironmentTests: XCTestCase {
|
||||||
var environment = Environment(loader: ExampleLoader())
|
private var environment = Environment(loader: ExampleLoader())
|
||||||
var template: Template = ""
|
private var template: Template = ""
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
@@ -26,6 +26,10 @@ final class EnvironmentTests: XCTestCase {
|
|||||||
template = ""
|
template = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
func testLoading() {
|
func testLoading() {
|
||||||
it("can load a template from a name") {
|
it("can load a template from a name") {
|
||||||
let template = try self.environment.loadTemplate(name: "example.html")
|
let template = try self.environment.loadTemplate(name: "example.html")
|
||||||
@@ -207,242 +211,11 @@ final class EnvironmentTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class EnvironmentIncludeTemplateTests: XCTestCase {
|
// MARK: - Helpers
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomTemplate: Template {
|
private class CustomTemplate: Template {
|
||||||
// swiftlint:disable discouraged_optional_collection
|
// swiftlint:disable discouraged_optional_collection
|
||||||
override func render(_ dictionary: [String: Any]? = nil) throws -> String {
|
override func render(_ dictionary: [String: Any]? = nil) throws -> String {
|
||||||
return "here"
|
"here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Spectre
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class ExpressionsTests: XCTestCase {
|
final class ExpressionsTests: XCTestCase {
|
||||||
let parser = TokenParser(tokens: [], environment: Environment())
|
private let parser = TokenParser(tokens: [], environment: Environment())
|
||||||
|
|
||||||
private func makeExpression(_ components: [String]) -> Expression {
|
private func makeExpression(_ components: [String]) -> Expression {
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -363,10 +363,12 @@ final class FilterTests: XCTestCase {
|
|||||||
Two
|
Two
|
||||||
"""
|
"""
|
||||||
]))
|
]))
|
||||||
|
// swiftlint:disable indentation_width
|
||||||
try expect(result) == """
|
try expect(result) == """
|
||||||
One
|
One
|
||||||
Two
|
Two
|
||||||
"""
|
"""
|
||||||
|
// swiftlint:enable indentation_width
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIndentNotEmptyLines() throws {
|
func testIndentNotEmptyLines() throws {
|
||||||
@@ -383,6 +385,7 @@ final class FilterTests: XCTestCase {
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
]))
|
]))
|
||||||
|
// swiftlint:disable indentation_width
|
||||||
try expect(result) == """
|
try expect(result) == """
|
||||||
One
|
One
|
||||||
|
|
||||||
@@ -391,6 +394,7 @@ final class FilterTests: XCTestCase {
|
|||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
// swiftlint:enable indentation_width
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDynamicFilters() throws {
|
func testDynamicFilters() throws {
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ final class FilterTagTests: XCTestCase {
|
|||||||
|
|
||||||
it("can render filters with arguments") {
|
it("can render filters with arguments") {
|
||||||
let ext = Extension()
|
let ext = Extension()
|
||||||
ext.registerFilter("split") {
|
ext.registerFilter("split") { value, args in
|
||||||
guard let value = $0 as? String,
|
guard let value = value as? String,
|
||||||
let argument = $1.first as? String else { return $0 }
|
let argument = args.first as? String else { return value }
|
||||||
return value.components(separatedBy: argument)
|
return value.components(separatedBy: argument)
|
||||||
}
|
}
|
||||||
let env = Environment(extensions: [ext])
|
let env = Environment(extensions: [ext])
|
||||||
@@ -37,11 +37,11 @@ final class FilterTagTests: XCTestCase {
|
|||||||
|
|
||||||
it("can render filters with quote as an argument") {
|
it("can render filters with quote as an argument") {
|
||||||
let ext = Extension()
|
let ext = Extension()
|
||||||
ext.registerFilter("replace") {
|
ext.registerFilter("replace") { value, args in
|
||||||
guard let value = $0 as? String,
|
guard let value = value as? String,
|
||||||
$1.count == 2,
|
args.count == 2,
|
||||||
let search = $1.first as? String,
|
let search = args.first as? String,
|
||||||
let replacement = $1.last as? String else { return $0 }
|
let replacement = args.last as? String else { return value }
|
||||||
return value.replacingOccurrences(of: search, with: replacement)
|
return value.replacingOccurrences(of: search, with: replacement)
|
||||||
}
|
}
|
||||||
let env = Environment(extensions: [ext])
|
let env = Environment(extensions: [ext])
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import Spectre
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class ForNodeTests: XCTestCase {
|
final class ForNodeTests: XCTestCase {
|
||||||
let context = Context(dictionary: [
|
private let context = Context(dictionary: [
|
||||||
"items": [1, 2, 3],
|
"items": [1, 2, 3],
|
||||||
"anyItems": [1, 2, 3] as [Any],
|
"anyItems": [1, 2, 3] as [Any],
|
||||||
|
// swiftlint:disable:next legacy_objc_type
|
||||||
"nsItems": NSArray(array: [1, 2, 3]),
|
"nsItems": NSArray(array: [1, 2, 3]),
|
||||||
"emptyItems": [Int](),
|
"emptyItems": [Int](),
|
||||||
"dict": [
|
"dict": [
|
||||||
@@ -313,6 +314,8 @@ final class ForNodeTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
private struct MyStruct {
|
private struct MyStruct {
|
||||||
let string: String
|
let string: String
|
||||||
let number: Int
|
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
|
@testable import Stencil
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
private struct SomeType {
|
|
||||||
let value: String? = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
final class IfNodeTests: XCTestCase {
|
final class IfNodeTests: XCTestCase {
|
||||||
func testParseIf() {
|
func testParseIf() {
|
||||||
it("can parse an if block") {
|
it("can parse an if block") {
|
||||||
@@ -286,3 +282,9 @@ final class IfNodeTests: XCTestCase {
|
|||||||
try expect(renderNodes(nodes, Context(dictionary: ["value": 4]))) == "false"
|
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
|
import XCTest
|
||||||
|
|
||||||
final class IncludeTests: XCTestCase {
|
final class IncludeTests: XCTestCase {
|
||||||
let path = Path(#file as String) + ".." + "fixtures"
|
private let path = Path(#file as String) + ".." + "fixtures"
|
||||||
lazy var loader = FileSystemLoader(paths: [path])
|
private lazy var loader = FileSystemLoader(paths: [path])
|
||||||
lazy var environment = Environment(loader: loader)
|
private lazy var environment = Environment(loader: loader)
|
||||||
|
|
||||||
func testParsing() {
|
func testParsing() {
|
||||||
it("throws an error when no template is given") {
|
it("throws an error when no template is given") {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import Stencil
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class InheritanceTests: XCTestCase {
|
final class InheritanceTests: XCTestCase {
|
||||||
let path = Path(#file as String) + ".." + "fixtures"
|
private let path = Path(#file as String) + ".." + "fixtures"
|
||||||
lazy var loader = FileSystemLoader(paths: [path])
|
private lazy var loader = FileSystemLoader(paths: [path])
|
||||||
lazy var environment = Environment(loader: loader)
|
private lazy var environment = Environment(loader: loader)
|
||||||
|
|
||||||
func testInheritance() {
|
func testInheritance() {
|
||||||
it("can inherit from another template") {
|
it("can inherit from another template") {
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ final class LexerTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNewlines() throws {
|
func testNewlines() throws {
|
||||||
|
// swiftlint:disable indentation_width
|
||||||
let templateString = """
|
let templateString = """
|
||||||
My name is {%
|
My name is {%
|
||||||
if name
|
if name
|
||||||
@@ -92,6 +93,7 @@ final class LexerTests: XCTestCase {
|
|||||||
}}{%
|
}}{%
|
||||||
endif %}.
|
endif %}.
|
||||||
"""
|
"""
|
||||||
|
// swiftlint:enable indentation_width
|
||||||
let lexer = Lexer(templateString: templateString)
|
let lexer = Lexer(templateString: templateString)
|
||||||
let tokens = lexer.tokenize()
|
let tokens = lexer.tokenize()
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,8 @@ import Spectre
|
|||||||
@testable import Stencil
|
@testable import Stencil
|
||||||
import XCTest
|
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 {
|
final class NodeTests: XCTestCase {
|
||||||
let context = Context(dictionary: [
|
private let context = Context(dictionary: [
|
||||||
"name": "Kyle",
|
"name": "Kyle",
|
||||||
"age": 27,
|
"age": 27,
|
||||||
"items": [1, 2, 3]
|
"items": [1, 2, 3]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class NowNodeTests: XCTestCase {
|
|||||||
|
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateFormat = "yyyy-MM-dd"
|
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
|
try expect(try node.render(Context())) == date
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ final class TokenParserTests: XCTestCase {
|
|||||||
|
|
||||||
try expect(try parser.parse()).toThrow(TemplateSyntaxError(
|
try expect(try parser.parse()).toThrow(TemplateSyntaxError(
|
||||||
reason: "Unknown template tag 'unknown'",
|
reason: "Unknown template tag 'unknown'",
|
||||||
token: tokens.first)
|
token: tokens.first
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,8 @@ import Spectre
|
|||||||
import Stencil
|
import Stencil
|
||||||
import XCTest
|
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 {
|
final class StencilTests: XCTestCase {
|
||||||
lazy var environment: Environment = {
|
private lazy var environment: Environment = {
|
||||||
let exampleExtension = Extension()
|
let exampleExtension = Extension()
|
||||||
exampleExtension.registerSimpleTag("simpletag") { _ in
|
exampleExtension.registerSimpleTag("simpletag") { _ in
|
||||||
"Hello World"
|
"Hello World"
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,47 +2,8 @@ import Spectre
|
|||||||
@testable import Stencil
|
@testable import Stencil
|
||||||
import XCTest
|
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 {
|
final class VariableTests: XCTestCase {
|
||||||
let context: Context = {
|
private let context: Context = {
|
||||||
let ext = Extension()
|
let ext = Extension()
|
||||||
ext.registerFilter("incr") { arg in
|
ext.registerFilter("incr") { arg in
|
||||||
(arg.flatMap { toNumber(value: $0) } ?? 0) + 1
|
(arg.flatMap { toNumber(value: $0) } ?? 0) + 1
|
||||||
@@ -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
|
fi
|
||||||
|
|
||||||
# possible paths
|
# possible paths
|
||||||
paths_sources="Sources"
|
paths_sources="Sources/Stencil"
|
||||||
paths_tests="Tests/StencilTests"
|
paths_tests="Tests/StencilTests"
|
||||||
|
|
||||||
# load selected group
|
# load selected group
|
||||||
|
|||||||
Reference in New Issue
Block a user