Add TuistTemplate tests.
This commit is contained in:
parent
cd5988308c
commit
47a1ff62fd
|
@ -1,7 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Error that occurs when parsing attributes from input
|
/// Error that occurs when parsing attributes from input
|
||||||
enum ParsingError: Error, CustomStringConvertible {
|
enum ParsingError: Error, CustomStringConvertible, Equatable {
|
||||||
/// Thrown when could not find attribute in input
|
/// Thrown when could not find attribute in input
|
||||||
case attributeNotFound(String)
|
case attributeNotFound(String)
|
||||||
/// Thrown when attributes not provided
|
/// Thrown when attributes not provided
|
||||||
|
@ -20,13 +20,14 @@ enum ParsingError: Error, CustomStringConvertible {
|
||||||
/// Returns value for `ParsedAttribute` of `name` from user input
|
/// Returns value for `ParsedAttribute` of `name` from user input
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - name: Name of `ParsedAttribute`
|
/// - name: Name of `ParsedAttribute`
|
||||||
|
/// - arguments: Arguments to get attribute from (usually you should lead this at default)
|
||||||
/// - Returns: Value of `ParsedAttribute`
|
/// - Returns: Value of `ParsedAttribute`
|
||||||
public func getAttribute(for name: String) throws -> String {
|
public func getAttribute(for name: String, arguments: [String] = CommandLine.arguments) throws -> String {
|
||||||
let jsonDecoder = JSONDecoder()
|
let jsonDecoder = JSONDecoder()
|
||||||
guard
|
guard
|
||||||
let attributesIndex = CommandLine.arguments.firstIndex(of: "--attributes"),
|
let attributesIndex = arguments.firstIndex(of: "--attributes") ?? arguments.firstIndex(of: "-a"),
|
||||||
CommandLine.arguments.endIndex > attributesIndex + 1,
|
arguments.endIndex > attributesIndex + 1,
|
||||||
let data = CommandLine.arguments[attributesIndex + 1].data(using: .utf8)
|
let data = arguments[attributesIndex + 1].data(using: .utf8)
|
||||||
else { throw ParsingError.attributesNotProvided }
|
else { throw ParsingError.attributesNotProvided }
|
||||||
|
|
||||||
let parsedAttributes = try jsonDecoder.decode([ParsedAttribute].self, from: data)
|
let parsedAttributes = try jsonDecoder.decode([ParsedAttribute].self, from: data)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Parsed attribute from user input
|
/// Parsed attribute from user input
|
||||||
public struct ParsedAttribute: Codable {
|
public struct ParsedAttribute: Codable, Equatable {
|
||||||
public init(name: String, value: String) {
|
public init(name: String, value: String) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Template manifest - used with `tuist scaffold`
|
/// Template manifest - used with `tuist scaffold`
|
||||||
public struct Template: Codable {
|
public struct Template: Codable, Equatable {
|
||||||
/// Description of template
|
/// Description of template
|
||||||
public let description: String
|
public let description: String
|
||||||
/// Attributes to be passed to template
|
/// Attributes to be passed to template
|
||||||
|
@ -12,19 +12,19 @@ public struct Template: Codable {
|
||||||
public let directories: [String]
|
public let directories: [String]
|
||||||
|
|
||||||
public init(description: String,
|
public init(description: String,
|
||||||
arguments: [Attribute] = [],
|
attributes: [Attribute] = [],
|
||||||
files: [File] = [],
|
files: [File] = [],
|
||||||
directories: [String] = [],
|
directories: [String] = [],
|
||||||
script: String? = nil) {
|
script: String? = nil) {
|
||||||
self.description = description
|
self.description = description
|
||||||
self.attributes = arguments
|
self.attributes = attributes
|
||||||
self.files = files
|
self.files = files
|
||||||
self.directories = directories
|
self.directories = directories
|
||||||
dumpIfNeeded(self)
|
dumpIfNeeded(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum containing information about how to generate file
|
/// Enum containing information about how to generate file
|
||||||
public enum Contents: Codable {
|
public enum Contents: Codable, Equatable {
|
||||||
/// Static Contents is defined in `Template.swift` and contains a simple `String`
|
/// Static Contents is defined in `Template.swift` and contains a simple `String`
|
||||||
/// Can not contain any additional logic apart from plain `String` from `arguments`
|
/// Can not contain any additional logic apart from plain `String` from `arguments`
|
||||||
case `static`(String)
|
case `static`(String)
|
||||||
|
@ -66,7 +66,7 @@ public struct Template: Codable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// File description for generating
|
/// File description for generating
|
||||||
public struct File: Codable {
|
public struct File: Codable, Equatable {
|
||||||
public let path: String
|
public let path: String
|
||||||
public let contents: Contents
|
public let contents: Contents
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ public struct Template: Codable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attribute to be passed to `tuist scaffold` for generating with `Template`
|
/// Attribute to be passed to `tuist scaffold` for generating with `Template`
|
||||||
public enum Attribute: Codable {
|
public enum Attribute: Codable, Equatable {
|
||||||
/// Required attribute with a given name
|
/// Required attribute with a given name
|
||||||
case required(String)
|
case required(String)
|
||||||
/// Optional attribute with a given name and a default value used when attribute not provided by user
|
/// Optional attribute with a given name and a default value used when attribute not provided by user
|
||||||
|
|
|
@ -110,7 +110,7 @@ class InitCommand: NSObject, Command {
|
||||||
let name = try self.name(arguments: arguments, path: path)
|
let name = try self.name(arguments: arguments, path: path)
|
||||||
try verifyDirectoryIsEmpty(path: path)
|
try verifyDirectoryIsEmpty(path: path)
|
||||||
|
|
||||||
let directories = try templatesDirectoryLocator.templateDirectories()
|
let directories = try templatesDirectoryLocator.templateDirectories(at: FileHandler.shared.currentPath)
|
||||||
if let template = arguments.get(templateArgument) {
|
if let template = arguments.get(templateArgument) {
|
||||||
guard
|
guard
|
||||||
let templateDirectory = directories.first(where: { $0.basename == template })
|
let templateDirectory = directories.first(where: { $0.basename == template })
|
||||||
|
|
|
@ -83,7 +83,7 @@ class ScaffoldCommand: NSObject, Command {
|
||||||
let path = self.path(arguments: arguments)
|
let path = self.path(arguments: arguments)
|
||||||
try verifyDirectoryIsEmpty(path: path)
|
try verifyDirectoryIsEmpty(path: path)
|
||||||
|
|
||||||
let directories = try templatesDirectoryLocator.templateDirectories()
|
let directories = try templatesDirectoryLocator.templateDirectories(at: FileHandler.shared.currentPath)
|
||||||
|
|
||||||
let shouldList = arguments.get(listArgument) ?? false
|
let shouldList = arguments.get(listArgument) ?? false
|
||||||
if shouldList {
|
if shouldList {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Basic
|
||||||
import Foundation
|
import Foundation
|
||||||
import TuistSupport
|
import TuistSupport
|
||||||
|
|
||||||
public protocol RootDirectoryLocating {
|
protocol RootDirectoryLocating {
|
||||||
/// Given a path, it finds the root directory by traversing up the hierarchy.
|
/// Given a path, it finds the root directory by traversing up the hierarchy.
|
||||||
/// The root directory is considered the directory that contains a Tuist/ directory or the directory where the
|
/// The root directory is considered the directory that contains a Tuist/ directory or the directory where the
|
||||||
/// git repository is defined if no Tuist/ directory is found.
|
/// git repository is defined if no Tuist/ directory is found.
|
||||||
|
@ -10,13 +10,13 @@ public protocol RootDirectoryLocating {
|
||||||
func locate(from path: AbsolutePath) -> AbsolutePath?
|
func locate(from path: AbsolutePath) -> AbsolutePath?
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class RootDirectoryLocator: RootDirectoryLocating {
|
final class RootDirectoryLocator: RootDirectoryLocating {
|
||||||
private let fileHandler: FileHandling = FileHandler.shared
|
private let fileHandler: FileHandling = FileHandler.shared
|
||||||
/// This cache avoids having to traverse the directories hierarchy every time the locate method is called.
|
/// This cache avoids having to traverse the directories hierarchy every time the locate method is called.
|
||||||
fileprivate var cache: [AbsolutePath: AbsolutePath] = [:]
|
fileprivate var cache: [AbsolutePath: AbsolutePath] = [:]
|
||||||
|
|
||||||
/// Shared instance
|
/// Shared instance
|
||||||
public static var shared: RootDirectoryLocating = RootDirectoryLocator()
|
static var shared: RootDirectoryLocating = RootDirectoryLocator()
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
internal init() {}
|
internal init() {}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/// Parsed attribute from user input
|
/// Parsed attribute from user input
|
||||||
public struct ParsedAttribute: Encodable {
|
public struct ParsedAttribute: Encodable, Equatable {
|
||||||
public init(name: String, value: String) {
|
public init(name: String, value: String) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
|
@ -7,9 +7,9 @@ public struct Template {
|
||||||
public let directories: [RelativePath]
|
public let directories: [RelativePath]
|
||||||
|
|
||||||
public init(description: String,
|
public init(description: String,
|
||||||
attributes: [Attribute],
|
attributes: [Attribute] = [],
|
||||||
files: [(path: RelativePath, contents: Contents)],
|
files: [(path: RelativePath, contents: Contents)] = [],
|
||||||
directories: [RelativePath]) {
|
directories: [RelativePath] = []) {
|
||||||
self.description = description
|
self.description = description
|
||||||
self.attributes = attributes
|
self.attributes = attributes
|
||||||
self.files = files
|
self.files = files
|
||||||
|
|
|
@ -3,6 +3,19 @@ import Foundation
|
||||||
import TuistSupport
|
import TuistSupport
|
||||||
import TuistLoader
|
import TuistLoader
|
||||||
|
|
||||||
|
enum TemplateGeneratorError: FatalError, Equatable {
|
||||||
|
var type: ErrorType { .abort }
|
||||||
|
|
||||||
|
case attributeNotFound(String)
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case let .attributeNotFound(attribute):
|
||||||
|
return "You must provide \(attribute) attribute"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Interface for generating content defined in template manifest
|
/// Interface for generating content defined in template manifest
|
||||||
public protocol TemplateGenerating {
|
public protocol TemplateGenerating {
|
||||||
/// Generate files for template manifest at `path`
|
/// Generate files for template manifest at `path`
|
||||||
|
@ -17,19 +30,16 @@ public protocol TemplateGenerating {
|
||||||
|
|
||||||
public final class TemplateGenerator: TemplateGenerating {
|
public final class TemplateGenerator: TemplateGenerating {
|
||||||
private let templateLoader: TemplateLoading
|
private let templateLoader: TemplateLoading
|
||||||
private let templateDescriptionHelpersBuilder: TemplateDescriptionHelpersBuilding
|
|
||||||
|
|
||||||
public init(templateLoader: TemplateLoading = TemplateLoader(),
|
public init(templateLoader: TemplateLoading = TemplateLoader()) {
|
||||||
templateDescriptionHelpersBuilder: TemplateDescriptionHelpersBuilding = TemplateDescriptionHelpersBuilder()) {
|
|
||||||
self.templateLoader = templateLoader
|
self.templateLoader = templateLoader
|
||||||
self.templateDescriptionHelpersBuilder = templateDescriptionHelpersBuilder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generate(at sourcePath: AbsolutePath,
|
public func generate(at sourcePath: AbsolutePath,
|
||||||
to destinationPath: AbsolutePath,
|
to destinationPath: AbsolutePath,
|
||||||
attributes: [String]) throws {
|
attributes: [String]) throws {
|
||||||
let template = try templateLoader.loadTemplate(at: sourcePath)
|
let template = try templateLoader.loadTemplate(at: sourcePath)
|
||||||
let templateAttributes = getTemplateAttributes(with: attributes, template: template)
|
let templateAttributes = try getTemplateAttributes(with: attributes, template: template)
|
||||||
|
|
||||||
try generateDirectories(template: template,
|
try generateDirectories(template: template,
|
||||||
templateAttributes: templateAttributes,
|
templateAttributes: templateAttributes,
|
||||||
|
@ -76,15 +86,17 @@ public final class TemplateGenerator: TemplateGenerating {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getTemplateAttributes(with attributes: [String], template: Template) -> [ParsedAttribute] {
|
private func getTemplateAttributes(with attributes: [String], template: Template) throws -> [ParsedAttribute] {
|
||||||
let parsedAttributes = parseAttributes(attributes)
|
let parsedAttributes = parseAttributes(attributes)
|
||||||
return template.attributes.map {
|
return try template.attributes.map {
|
||||||
switch $0 {
|
switch $0 {
|
||||||
case let .optional(name, default: defaultValue):
|
case let .optional(name, default: defaultValue):
|
||||||
let value = parsedAttributes.first(where: { $0.name == name })?.value ?? defaultValue
|
let value = parsedAttributes.first(where: { $0.name == name })?.value ?? defaultValue
|
||||||
return ParsedAttribute(name: name, value: value)
|
return ParsedAttribute(name: name, value: value)
|
||||||
case let .required(name):
|
case let .required(name):
|
||||||
guard let value = parsedAttributes.first(where: { $0.name == name })?.value else { fatalError() }
|
guard
|
||||||
|
let value = parsedAttributes.first(where: { $0.name == name })?.value
|
||||||
|
else { throw TemplateGeneratorError.attributeNotFound(name) }
|
||||||
return ParsedAttribute(name: name, value: value)
|
return ParsedAttribute(name: name, value: value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,21 @@ import TuistSupport
|
||||||
import TemplateDescription
|
import TemplateDescription
|
||||||
import TuistLoader
|
import TuistLoader
|
||||||
|
|
||||||
|
public enum TemplateLoaderError: FatalError, Equatable {
|
||||||
|
public var type: ErrorType { .abort }
|
||||||
|
|
||||||
|
case manifestNotFound(AbsolutePath)
|
||||||
|
case generateFileNotFound(AbsolutePath)
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case let .manifestNotFound(manifestPath):
|
||||||
|
return "Could not find template manifest at \(manifestPath.pathString)"
|
||||||
|
case let .generateFileNotFound(generateFilePath):
|
||||||
|
return "Could not find generate file at \(generateFilePath.pathString)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public protocol TemplateLoading {
|
public protocol TemplateLoading {
|
||||||
/// Load `TuistTemplate.Template` at given `path`
|
/// Load `TuistTemplate.Template` at given `path`
|
||||||
|
@ -47,7 +62,7 @@ public class TemplateLoader: TemplateLoading {
|
||||||
public func loadTemplate(at path: AbsolutePath) throws -> TuistTemplate.Template {
|
public func loadTemplate(at path: AbsolutePath) throws -> TuistTemplate.Template {
|
||||||
let manifestPath = path.appending(component: "Template.swift")
|
let manifestPath = path.appending(component: "Template.swift")
|
||||||
guard FileHandler.shared.exists(manifestPath) else {
|
guard FileHandler.shared.exists(manifestPath) else {
|
||||||
fatalError()
|
throw TemplateLoaderError.manifestNotFound(manifestPath)
|
||||||
}
|
}
|
||||||
let data = try loadManifestData(at: manifestPath)
|
let data = try loadManifestData(at: manifestPath)
|
||||||
let manifest = try decoder.decode(TemplateDescription.Template.self, from: data)
|
let manifest = try decoder.decode(TemplateDescription.Template.self, from: data)
|
||||||
|
@ -56,6 +71,9 @@ public class TemplateLoader: TemplateLoading {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadGenerateFile(at path: AbsolutePath, parsedAttributes: [TuistTemplate.ParsedAttribute]) throws -> String {
|
public func loadGenerateFile(at path: AbsolutePath, parsedAttributes: [TuistTemplate.ParsedAttribute]) throws -> String {
|
||||||
|
guard FileHandler.shared.exists(path) else {
|
||||||
|
throw TemplateLoaderError.generateFileNotFound(path)
|
||||||
|
}
|
||||||
var additionalArguments: [String] = []
|
var additionalArguments: [String] = []
|
||||||
if let attributes = try String(data: encoder.encode(parsedAttributes), encoding: .utf8) {
|
if let attributes = try String(data: encoder.encode(parsedAttributes), encoding: .utf8) {
|
||||||
additionalArguments.append("--attributes")
|
additionalArguments.append("--attributes")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import Basic
|
import Basic
|
||||||
import Foundation
|
import Foundation
|
||||||
import TuistSupport
|
import TuistSupport
|
||||||
import TuistLoader
|
|
||||||
|
|
||||||
public protocol TemplatesDirectoryLocating {
|
public protocol TemplatesDirectoryLocating {
|
||||||
/// Returns the path to the tuist built-in templates directory if it exists.
|
/// Returns the path to the tuist built-in templates directory if it exists.
|
||||||
|
@ -12,27 +11,16 @@ public protocol TemplatesDirectoryLocating {
|
||||||
/// - Returns: Path of templates directory up the three `from`
|
/// - Returns: Path of templates directory up the three `from`
|
||||||
func locate(from path: AbsolutePath) -> AbsolutePath?
|
func locate(from path: AbsolutePath) -> AbsolutePath?
|
||||||
/// - Returns: All available directories with defined templates (custom and built-in)
|
/// - Returns: All available directories with defined templates (custom and built-in)
|
||||||
func templateDirectories() throws -> [AbsolutePath]
|
func templateDirectories(at path: AbsolutePath) throws -> [AbsolutePath]
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
public final class TemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
||||||
private let fileHandler: FileHandling = FileHandler.shared
|
private let fileHandler: FileHandling = FileHandler.shared
|
||||||
/// This cache avoids having to traverse the directories hierarchy every time the locate method is called.
|
/// This cache avoids having to traverse the directories hierarchy every time the locate method is called.
|
||||||
private var cache: [AbsolutePath: AbsolutePath] = [:]
|
private var cache: [AbsolutePath: AbsolutePath] = [:]
|
||||||
|
|
||||||
/// Instance to locate the root directory of the project.
|
|
||||||
let rootDirectoryLocator: RootDirectoryLocating
|
|
||||||
|
|
||||||
/// Default constructor.
|
/// Default constructor.
|
||||||
public convenience init() {
|
public init() { }
|
||||||
self.init(rootDirectoryLocator: RootDirectoryLocator.shared)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes the locator with its dependencies.
|
|
||||||
/// - Parameter rootDirectoryLocator: Instance to locate the root directory of the project.
|
|
||||||
init(rootDirectoryLocator: RootDirectoryLocating) {
|
|
||||||
self.rootDirectoryLocator = rootDirectoryLocator
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - TemplatesDirectoryLocating
|
// MARK: - TemplatesDirectoryLocating
|
||||||
|
|
||||||
|
@ -56,9 +44,7 @@ public final class TemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func locateCustom(at: AbsolutePath) -> AbsolutePath? {
|
public func locateCustom(at: AbsolutePath) -> AbsolutePath? {
|
||||||
guard let rootDirectory = rootDirectoryLocator.locate(from: at) else { return nil }
|
guard let customTemplatesDirectory = locate(from: at) else { return nil }
|
||||||
let customTemplatesDirectory = rootDirectory
|
|
||||||
.appending(components: Constants.tuistDirectoryName, Constants.templatesDirectoryName)
|
|
||||||
if !FileHandler.shared.exists(customTemplatesDirectory) { return nil }
|
if !FileHandler.shared.exists(customTemplatesDirectory) { return nil }
|
||||||
return customTemplatesDirectory
|
return customTemplatesDirectory
|
||||||
}
|
}
|
||||||
|
@ -67,10 +53,10 @@ public final class TemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
||||||
locate(from: path, source: path)
|
locate(from: path, source: path)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func templateDirectories() throws -> [AbsolutePath] {
|
public func templateDirectories(at path: AbsolutePath) throws -> [AbsolutePath] {
|
||||||
let templatesDirectory = locate()
|
let templatesDirectory = locate()
|
||||||
let templates = try templatesDirectory.map(FileHandler.shared.contentsOfDirectory) ?? []
|
let templates = try templatesDirectory.map(FileHandler.shared.contentsOfDirectory) ?? []
|
||||||
let customTemplatesDirectory = locateCustom(at: FileHandler.shared.currentPath)
|
let customTemplatesDirectory = locateCustom(at: path)
|
||||||
let customTemplates = try customTemplatesDirectory.map(FileHandler.shared.contentsOfDirectory) ?? []
|
let customTemplates = try customTemplatesDirectory.map(FileHandler.shared.contentsOfDirectory) ?? []
|
||||||
return (templates + customTemplates).filter { $0.basename != Constants.templateHelpersDirectoryName }
|
return (templates + customTemplates).filter { $0.basename != Constants.templateHelpersDirectoryName }
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ public final class MockTemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
||||||
public var locateStub: (() -> AbsolutePath?)?
|
public var locateStub: (() -> AbsolutePath?)?
|
||||||
public var locateCustomStub: ((AbsolutePath) -> AbsolutePath?)?
|
public var locateCustomStub: ((AbsolutePath) -> AbsolutePath?)?
|
||||||
public var locateFromStub: ((AbsolutePath) -> AbsolutePath?)?
|
public var locateFromStub: ((AbsolutePath) -> AbsolutePath?)?
|
||||||
public var templateDirectoriesStub: (() throws -> [AbsolutePath])?
|
public var templateDirectoriesStub: ((AbsolutePath) throws -> [AbsolutePath])?
|
||||||
|
|
||||||
public func locate() -> AbsolutePath? {
|
public func locate() -> AbsolutePath? {
|
||||||
locateStub?()
|
locateStub?()
|
||||||
|
@ -21,8 +21,8 @@ public final class MockTemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
||||||
locateFromStub?(path)
|
locateFromStub?(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func templateDirectories() throws -> [AbsolutePath] {
|
public func templateDirectories(at path: AbsolutePath) throws -> [AbsolutePath] {
|
||||||
try templateDirectoriesStub?() ?? []
|
try templateDirectoriesStub?(path) ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import TemplateDescription
|
import TemplateDescription
|
||||||
|
|
||||||
let nameArgument: Template.Attribute = .required("name")
|
let nameAttribute: Template.Attribute = .required("name")
|
||||||
let platformArgument: Template.Attribute = .optional("platform", default: "iOS")
|
let platformAttribute: Template.Attribute = .optional("platform", default: "iOS")
|
||||||
|
|
||||||
let setupContent = """
|
let setupContent = """
|
||||||
import ProjectDescription
|
import ProjectDescription
|
||||||
|
@ -59,9 +59,9 @@ extension Project {
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let projectsPath = "Projects"
|
let projectsPath = "Projects"
|
||||||
let appPath = projectsPath + "/\(nameArgument)"
|
let appPath = projectsPath + "/\(nameAttribute)"
|
||||||
let kitFrameworkPath = projectsPath + "/\(nameArgument)Kit"
|
let kitFrameworkPath = projectsPath + "/\(nameAttribute)Kit"
|
||||||
let supportFrameworkPath = projectsPath + "/\(nameArgument)Support"
|
let supportFrameworkPath = projectsPath + "/\(nameAttribute)Support"
|
||||||
|
|
||||||
func directories(for projectPath: String) -> [String] {
|
func directories(for projectPath: String) -> [String] {
|
||||||
[
|
[
|
||||||
|
@ -76,10 +76,10 @@ let workspaceContent = """
|
||||||
import ProjectDescription
|
import ProjectDescription
|
||||||
import ProjectDescriptionHelpers
|
import ProjectDescriptionHelpers
|
||||||
|
|
||||||
let workspace = Workspace(name: "\(nameArgument)", projects: [
|
let workspace = Workspace(name: "\(nameAttribute)", projects: [
|
||||||
"Projects/\(nameArgument)",
|
"Projects/\(nameAttribute)",
|
||||||
"Projects/\(nameArgument)Kit",
|
"Projects/\(nameAttribute)Kit",
|
||||||
"Projects/\(nameArgument)Support"
|
"Projects/\(nameAttribute)Support"
|
||||||
])
|
])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -98,15 +98,15 @@ func testsContent(_ name: String) -> String {
|
||||||
|
|
||||||
let kitSourceContent = """
|
let kitSourceContent = """
|
||||||
import Foundation
|
import Foundation
|
||||||
import \(nameArgument)Support
|
import \(nameAttribute)Support
|
||||||
|
|
||||||
public final class \(nameArgument)Kit {}
|
public final class \(nameAttribute)Kit {}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let supportSourceContent = """
|
let supportSourceContent = """
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public final class \(nameArgument)Support {}
|
public final class \(nameAttribute)Support {}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let playgroundContent = """
|
let playgroundContent = """
|
||||||
|
@ -193,10 +193,10 @@ graph.dot
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let template = Template(
|
let template = Template(
|
||||||
description: "Custom \(nameArgument)",
|
description: "Custom \(nameAttribute)",
|
||||||
arguments: [
|
attributes: [
|
||||||
nameArgument,
|
nameAttribute,
|
||||||
platformArgument,
|
platformAttribute,
|
||||||
],
|
],
|
||||||
files: [
|
files: [
|
||||||
.static(path: "Setup.swift",
|
.static(path: "Setup.swift",
|
||||||
|
@ -213,23 +213,23 @@ let template = Template(
|
||||||
generateFilePath: "SupportFrameworkProject.swift"),
|
generateFilePath: "SupportFrameworkProject.swift"),
|
||||||
.generated(path: appPath + "/Sources/AppDelegate.swift",
|
.generated(path: appPath + "/Sources/AppDelegate.swift",
|
||||||
generateFilePath: "AppDelegate.swift"),
|
generateFilePath: "AppDelegate.swift"),
|
||||||
.static(path: appPath + "/Tests/\(nameArgument)Tests.swift",
|
.static(path: appPath + "/Tests/\(nameAttribute)Tests.swift",
|
||||||
contents: testsContent("\(nameArgument)")),
|
contents: testsContent("\(nameAttribute)")),
|
||||||
.static(path: kitFrameworkPath + "/Sources/\(nameArgument)Kit.swift",
|
.static(path: kitFrameworkPath + "/Sources/\(nameAttribute)Kit.swift",
|
||||||
contents: kitSourceContent),
|
contents: kitSourceContent),
|
||||||
.static(path: kitFrameworkPath + "/Tests/\(nameArgument)KitTests.swift",
|
.static(path: kitFrameworkPath + "/Tests/\(nameAttribute)KitTests.swift",
|
||||||
contents: testsContent("\(nameArgument)Kit")),
|
contents: testsContent("\(nameAttribute)Kit")),
|
||||||
.static(path: supportFrameworkPath + "/Sources/\(nameArgument)Support.swift",
|
.static(path: supportFrameworkPath + "/Sources/\(nameAttribute)Support.swift",
|
||||||
contents: supportSourceContent),
|
contents: supportSourceContent),
|
||||||
.static(path: supportFrameworkPath + "/Tests/\(nameArgument)SupportTests.swift",
|
.static(path: supportFrameworkPath + "/Tests/\(nameAttribute)SupportTests.swift",
|
||||||
contents: testsContent("\(nameArgument)Support")),
|
contents: testsContent("\(nameAttribute)Support")),
|
||||||
.static(path: kitFrameworkPath + "/Playgrounds/\(nameArgument)Kit.playground" + "/Contents.swift",
|
.static(path: kitFrameworkPath + "/Playgrounds/\(nameAttribute)Kit.playground" + "/Contents.swift",
|
||||||
contents: playgroundContent),
|
contents: playgroundContent),
|
||||||
.generated(path: kitFrameworkPath + "/Playgrounds/\(nameArgument)Kit.playground" + "/contents.xcplayground",
|
.generated(path: kitFrameworkPath + "/Playgrounds/\(nameAttribute)Kit.playground" + "/contents.xcplayground",
|
||||||
generateFilePath: "Playground.swift"),
|
generateFilePath: "Playground.swift"),
|
||||||
.static(path: supportFrameworkPath + "/Playgrounds/\(nameArgument)Support.playground" + "/Contents.swift",
|
.static(path: supportFrameworkPath + "/Playgrounds/\(nameAttribute)Support.playground" + "/Contents.swift",
|
||||||
contents: playgroundContent),
|
contents: playgroundContent),
|
||||||
.generated(path: supportFrameworkPath + "/Playgrounds/\(nameArgument)Support.playground" + "/contents.xcplayground",
|
.generated(path: supportFrameworkPath + "/Playgrounds/\(nameAttribute)Support.playground" + "/contents.xcplayground",
|
||||||
generateFilePath: "Playground.swift"),
|
generateFilePath: "Playground.swift"),
|
||||||
.static(path: "TuistConfig.swift",
|
.static(path: "TuistConfig.swift",
|
||||||
contents: tuistConfigContent),
|
contents: tuistConfigContent),
|
||||||
|
@ -238,8 +238,8 @@ let template = Template(
|
||||||
],
|
],
|
||||||
directories: [
|
directories: [
|
||||||
"Tuist/ProjectDescriptionHelpers",
|
"Tuist/ProjectDescriptionHelpers",
|
||||||
supportFrameworkPath + "/Playgrounds/\(nameArgument)Support.playground",
|
supportFrameworkPath + "/Playgrounds/\(nameAttribute)Support.playground",
|
||||||
kitFrameworkPath + "/Playgrounds/\(nameArgument)Kit.playground",
|
kitFrameworkPath + "/Playgrounds/\(nameAttribute)Kit.playground",
|
||||||
]
|
]
|
||||||
+ directories(for: appPath)
|
+ directories(for: appPath)
|
||||||
+ directories(for: kitFrameworkPath)
|
+ directories(for: kitFrameworkPath)
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import TuistSupportTesting
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import TemplateDescription
|
||||||
|
|
||||||
|
class ParsedAttributeTests: XCTestCase {
|
||||||
|
func test_parsed_attribute_codable() throws {
|
||||||
|
// Given
|
||||||
|
let parsedAttribute = ParsedAttribute(name: "name", value: "value name")
|
||||||
|
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertCodable(parsedAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_getAttributes_when_attribute_present() throws {
|
||||||
|
// Given
|
||||||
|
let parsedAttributes: [ParsedAttribute] = [
|
||||||
|
ParsedAttribute(name: "a", value: "a value"),
|
||||||
|
ParsedAttribute(name: "b", value: "b value")
|
||||||
|
]
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let parsedAttributesString = String(data: try encoder.encode(parsedAttributes), encoding: .utf8)
|
||||||
|
let arguments = ["tuist", "something", "--attributes", parsedAttributesString ?? ""]
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(try getAttribute(for: "a", arguments: arguments), "a value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_getAttributes_when_attribute_present_and_short_option() throws {
|
||||||
|
// Given
|
||||||
|
let parsedAttributes: [ParsedAttribute] = [
|
||||||
|
ParsedAttribute(name: "a", value: "a value"),
|
||||||
|
ParsedAttribute(name: "b", value: "b value")
|
||||||
|
]
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let parsedAttributesString = String(data: try encoder.encode(parsedAttributes), encoding: .utf8)
|
||||||
|
let arguments = ["tuist", "something", "-a", parsedAttributesString ?? ""]
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(try getAttribute(for: "a", arguments: arguments), "a value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_getAttributes_error_when_attribute_not_present() throws {
|
||||||
|
// Given
|
||||||
|
let parsedAttributes: [ParsedAttribute] = [
|
||||||
|
ParsedAttribute(name: "b", value: "b value")
|
||||||
|
]
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let parsedAttributesString = String(data: try encoder.encode(parsedAttributes), encoding: .utf8)
|
||||||
|
let arguments = ["tuist", "something", "-a", parsedAttributesString ?? ""]
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertThrowsSpecific(try getAttribute(for: "a", arguments: arguments), ParsingError.attributeNotFound("a"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_getAttributes_error_when_attributes_not_provided() throws {
|
||||||
|
// Given
|
||||||
|
let arguments = ["tuist", "something", "--not-attributes"]
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertThrowsSpecific(try getAttribute(for: "a", arguments: arguments), ParsingError.attributesNotProvided)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import TemplateDescription
|
||||||
|
import TuistSupportTesting
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class TemplateTests: XCTestCase {
|
||||||
|
func test_template_codable() throws {
|
||||||
|
// Given
|
||||||
|
let template = Template(
|
||||||
|
description: "",
|
||||||
|
attributes: [
|
||||||
|
.required("name"),
|
||||||
|
.optional("aName", default: "defaultName"),
|
||||||
|
.optional("bName", default: ""),
|
||||||
|
],
|
||||||
|
files: [
|
||||||
|
.static(path: "static.swift", contents: "content"),
|
||||||
|
.generated(path: "generated.swift", generateFilePath: "generate.swift")
|
||||||
|
],
|
||||||
|
directories: [
|
||||||
|
"{{ name }}",
|
||||||
|
"directory"
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertCodable(template)
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ final class InitCommandTests: TuistUnitTestCase {
|
||||||
// Given
|
// Given
|
||||||
let templateName = "template"
|
let templateName = "template"
|
||||||
let templatePath = try temporaryPath().appending(component: templateName)
|
let templatePath = try temporaryPath().appending(component: templateName)
|
||||||
templatesDirectoryLocator.templateDirectoriesStub = {
|
templatesDirectoryLocator.templateDirectoriesStub = { _ in
|
||||||
[templatePath]
|
[templatePath]
|
||||||
}
|
}
|
||||||
var generateSourcePath: AbsolutePath?
|
var generateSourcePath: AbsolutePath?
|
||||||
|
@ -81,7 +81,7 @@ final class InitCommandTests: TuistUnitTestCase {
|
||||||
func test_init_default_when_no_template() throws {
|
func test_init_default_when_no_template() throws {
|
||||||
// Given
|
// Given
|
||||||
let defaultTemplatePath = try temporaryPath().appending(component: "default")
|
let defaultTemplatePath = try temporaryPath().appending(component: "default")
|
||||||
templatesDirectoryLocator.templateDirectoriesStub = {
|
templatesDirectoryLocator.templateDirectoriesStub = { _ in
|
||||||
[defaultTemplatePath]
|
[defaultTemplatePath]
|
||||||
}
|
}
|
||||||
let attributes = ["--name", "name", "--platform", "macos"]
|
let attributes = ["--name", "name", "--platform", "macos"]
|
||||||
|
@ -100,7 +100,7 @@ final class InitCommandTests: TuistUnitTestCase {
|
||||||
|
|
||||||
func test_init_default_platform() throws {
|
func test_init_default_platform() throws {
|
||||||
let defaultTemplatePath = try temporaryPath().appending(component: "default")
|
let defaultTemplatePath = try temporaryPath().appending(component: "default")
|
||||||
templatesDirectoryLocator.templateDirectoriesStub = {
|
templatesDirectoryLocator.templateDirectoriesStub = { _ in
|
||||||
[defaultTemplatePath]
|
[defaultTemplatePath]
|
||||||
}
|
}
|
||||||
let attributes = ["--name", "name"]
|
let attributes = ["--name", "name"]
|
||||||
|
|
|
@ -60,7 +60,7 @@ final class ScaffoldCommandTests: TuistUnitTestCase {
|
||||||
// Given
|
// Given
|
||||||
let templateName = "template"
|
let templateName = "template"
|
||||||
let templatePath = try temporaryPath().appending(component: templateName)
|
let templatePath = try temporaryPath().appending(component: templateName)
|
||||||
templatesDirectoryLocator.templateDirectoriesStub = {
|
templatesDirectoryLocator.templateDirectoriesStub = { _ in
|
||||||
[templatePath]
|
[templatePath]
|
||||||
}
|
}
|
||||||
var generateSourcePath: AbsolutePath?
|
var generateSourcePath: AbsolutePath?
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import Basic
|
||||||
|
import Foundation
|
||||||
|
import TuistSupport
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import TuistTemplate
|
||||||
|
@testable import TuistSupportTesting
|
||||||
|
|
||||||
|
final class TemplateLoaderTests: TuistTestCase {
|
||||||
|
var subject: TemplateLoader!
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
subject = TemplateLoader()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
super.tearDown()
|
||||||
|
subject = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_loadTemplate() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryPath = try self.temporaryPath()
|
||||||
|
let content = """
|
||||||
|
import TemplateDescription
|
||||||
|
|
||||||
|
let template = Template(
|
||||||
|
description: "Template description"
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
let manifestPath = temporaryPath.appending(component: "Template.swift")
|
||||||
|
try content.write(to: manifestPath.url,
|
||||||
|
atomically: true,
|
||||||
|
encoding: .utf8)
|
||||||
|
|
||||||
|
// When
|
||||||
|
let got = try subject.loadTemplate(at: temporaryPath)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(got.description, "Template description")
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_loadGenerateFile() throws {
|
||||||
|
let temporaryPath = try self.temporaryPath()
|
||||||
|
let content = """
|
||||||
|
import Foundation
|
||||||
|
import TemplateDescription
|
||||||
|
|
||||||
|
let content = Content {
|
||||||
|
let name = try getAttribute(for: "name")
|
||||||
|
return "name: \\(name)"
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
let expectedContent = "name: test name"
|
||||||
|
let generateFilePath = temporaryPath.appending(component: "Generate.swift")
|
||||||
|
try content.write(to: generateFilePath.url,
|
||||||
|
atomically: true,
|
||||||
|
encoding: .utf8)
|
||||||
|
// When
|
||||||
|
let got = try subject.loadGenerateFile(at: generateFilePath,
|
||||||
|
parsedAttributes: [ParsedAttribute(name: "name", value: "test name")])
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(got, expectedContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_load_invalidFormat() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryPath = try self.temporaryPath()
|
||||||
|
let content = """
|
||||||
|
import ABC
|
||||||
|
let template
|
||||||
|
"""
|
||||||
|
|
||||||
|
let manifestPath = temporaryPath.appending(component: "Template.swift")
|
||||||
|
try content.write(to: manifestPath.url,
|
||||||
|
atomically: true,
|
||||||
|
encoding: .utf8)
|
||||||
|
|
||||||
|
// When / Then
|
||||||
|
XCTAssertThrowsError(
|
||||||
|
try subject.loadTemplate(at: temporaryPath)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_load_missingManifest() throws {
|
||||||
|
let temporaryPath = try self.temporaryPath()
|
||||||
|
XCTAssertThrowsSpecific(try subject.loadTemplate(at: temporaryPath),
|
||||||
|
TemplateLoaderError.manifestNotFound(temporaryPath.appending(components: "Template.swift")))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
import Basic
|
||||||
|
import Foundation
|
||||||
|
import TuistSupport
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import TuistTemplate
|
||||||
|
@testable import TuistSupportTesting
|
||||||
|
|
||||||
|
final class TemplatesDirectoryLocatorIntegrationTests: TuistTestCase {
|
||||||
|
var subject: TemplatesDirectoryLocator!
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
subject = TemplatesDirectoryLocator()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
subject = nil
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_locate_when_a_templates_and_git_directory_exists() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryDirectory = try temporaryPath()
|
||||||
|
try createFolders(["this/is/a/very/nested/directory", "this/is/Tuist/Templates", "this/.git"])
|
||||||
|
|
||||||
|
// When
|
||||||
|
let got = subject.locate(from: temporaryDirectory.appending(RelativePath("this/is/a/very/nested/directory")))
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(got, temporaryDirectory.appending(RelativePath("this/is/Tuist/Templates")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_locate_when_a_templates_directory_exists() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryDirectory = try temporaryPath()
|
||||||
|
try createFolders(["this/is/a/very/nested/directory", "this/is/Tuist/Templates"])
|
||||||
|
|
||||||
|
// When
|
||||||
|
let got = subject.locate(from: temporaryDirectory.appending(RelativePath("this/is/a/very/nested/directory")))
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(got, temporaryDirectory.appending(RelativePath("this/is/Tuist/Templates")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_locate_when_a_git_directory_exists() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryDirectory = try temporaryPath()
|
||||||
|
try createFolders(["this/is/a/very/nested/directory", "this/.git"])
|
||||||
|
|
||||||
|
// When
|
||||||
|
let got = subject.locate(from: temporaryDirectory.appending(RelativePath("this/is/a/very/nested/directory")))
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(got, temporaryDirectory.appending(RelativePath("this/Tuist/Templates")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_locate_when_multiple_tuist_directories_exists() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryDirectory = try temporaryPath()
|
||||||
|
try createFolders(["this/is/a/very/nested/Tuist/", "this/is/Tuist/"])
|
||||||
|
let paths = [
|
||||||
|
"this/is/a/very/directory",
|
||||||
|
"this/is/a/very/nested/directory",
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let got = paths.map {
|
||||||
|
subject.locate(from: temporaryDirectory.appending(RelativePath($0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(got, [
|
||||||
|
"this/is/Tuist/Templates",
|
||||||
|
"this/is/a/very/nested/Tuist/Templates",
|
||||||
|
].map { temporaryDirectory.appending(RelativePath($0)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_locate_when_templates_directory_exist() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryDirectory = try temporaryPath()
|
||||||
|
try createFolders(["this/is/a/very/nested/directory", "this/Templates"])
|
||||||
|
// When
|
||||||
|
let got = subject.locate(from: temporaryDirectory.appending(RelativePath("this/is/a/very/nested/directory")))
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(got, temporaryDirectory.appending(RelativePath("this/Templates")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_locate_all_templates() throws {
|
||||||
|
// Given
|
||||||
|
let temporaryDirectory = try temporaryPath()
|
||||||
|
try createFolders(["this/is/a/directory", "this/Templates/template_one", "this/Templates/template_two"])
|
||||||
|
|
||||||
|
// When
|
||||||
|
let got = try subject.templateDirectories(at: temporaryDirectory.appending(RelativePath("this/is/a/directory")))
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual([
|
||||||
|
AbsolutePath(#file.replacingOccurrences(of: "file://", with: ""))
|
||||||
|
.appending(RelativePath("../../../../Templates/default")),
|
||||||
|
temporaryDirectory.appending(RelativePath("this/Templates/template_one")),
|
||||||
|
temporaryDirectory.appending(RelativePath("this/Templates/template_two")),
|
||||||
|
], got)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
import Basic
|
||||||
|
import Foundation
|
||||||
|
import TuistSupport
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import TuistTemplateTesting
|
||||||
|
@testable import TuistSupportTesting
|
||||||
|
@testable import TuistTemplate
|
||||||
|
|
||||||
|
final class TemplateGeneratorTests: TuistTestCase {
|
||||||
|
var subject: TemplateGenerator!
|
||||||
|
var templateLoader: MockTemplateLoader!
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
templateLoader = MockTemplateLoader()
|
||||||
|
subject = TemplateGenerator(templateLoader: templateLoader)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
super.tearDown()
|
||||||
|
subject = nil
|
||||||
|
templateLoader = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_directories_are_generated() throws {
|
||||||
|
// Given
|
||||||
|
let directories = [RelativePath("a"), RelativePath("a/b"), RelativePath("c")]
|
||||||
|
templateLoader.loadTemplateStub = { _ in
|
||||||
|
Template(description: "",
|
||||||
|
directories: directories)
|
||||||
|
}
|
||||||
|
let destinationPath = try temporaryPath()
|
||||||
|
let expectedDirectories = directories.map(destinationPath.appending)
|
||||||
|
|
||||||
|
// When
|
||||||
|
try subject.generate(at: try temporaryPath(),
|
||||||
|
to: destinationPath,
|
||||||
|
attributes: [])
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertTrue(expectedDirectories.allSatisfy(FileHandler.shared.exists))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_directories_with_attributes() throws {
|
||||||
|
// Given
|
||||||
|
let name = "GenName"
|
||||||
|
let bName = "BName"
|
||||||
|
let defaultName = "defaultName"
|
||||||
|
let directories = [RelativePath("{{ name }}"), RelativePath("{{ aName }}"), RelativePath("{{ bName }}")]
|
||||||
|
templateLoader.loadTemplateStub = { _ in
|
||||||
|
Template(description: "",
|
||||||
|
attributes: [
|
||||||
|
.required("name"),
|
||||||
|
.optional("aName", default: defaultName),
|
||||||
|
.optional("bName", default: ""),
|
||||||
|
],
|
||||||
|
directories: directories)
|
||||||
|
}
|
||||||
|
let destinationPath = try temporaryPath()
|
||||||
|
let expectedDirectories = [name, defaultName, bName].map(destinationPath.appending)
|
||||||
|
|
||||||
|
// When
|
||||||
|
try subject.generate(at: try temporaryPath(),
|
||||||
|
to: destinationPath,
|
||||||
|
attributes: ["--name", name, "--bName", bName])
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertTrue(expectedDirectories.allSatisfy(FileHandler.shared.exists))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_fails_when_required_attribute_not_provided() throws {
|
||||||
|
// Given
|
||||||
|
templateLoader.loadTemplateStub = { _ in
|
||||||
|
Template(description: "",
|
||||||
|
attributes: [.required("required")]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertThrowsSpecific(try subject.generate(at: try temporaryPath(),
|
||||||
|
to: try temporaryPath(),
|
||||||
|
attributes: []),
|
||||||
|
TemplateGeneratorError.attributeNotFound("required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_files_are_generated() throws {
|
||||||
|
// Given
|
||||||
|
let files: [(path: RelativePath, contents: Template.Contents)] = [
|
||||||
|
(path: RelativePath("a"), contents: .static("aContent")),
|
||||||
|
(path: RelativePath("b"), contents: .static("bContent")),
|
||||||
|
]
|
||||||
|
templateLoader.loadTemplateStub = { _ in
|
||||||
|
Template(description: "",
|
||||||
|
files: files)
|
||||||
|
}
|
||||||
|
let destinationPath = try temporaryPath()
|
||||||
|
let expectedFiles: [(AbsolutePath, String)] = files.compactMap {
|
||||||
|
let content: String
|
||||||
|
switch $0.contents {
|
||||||
|
case let .static(staticContent):
|
||||||
|
content = staticContent
|
||||||
|
case .generated:
|
||||||
|
XCTFail("Unexpected type")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (destinationPath.appending($0.path), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
try subject.generate(at: try temporaryPath(),
|
||||||
|
to: destinationPath,
|
||||||
|
attributes: [])
|
||||||
|
|
||||||
|
// Then
|
||||||
|
try expectedFiles.forEach {
|
||||||
|
XCTAssertEqual(try FileHandler.shared.readTextFile($0.0), $0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_files_are_generated_with_attributes() throws {
|
||||||
|
// Given
|
||||||
|
templateLoader.loadTemplateStub = { _ in
|
||||||
|
Template(description: "",
|
||||||
|
attributes: [
|
||||||
|
.required("name"),
|
||||||
|
.required("contentName"),
|
||||||
|
.required("directoryName"),
|
||||||
|
.required("fileName"),
|
||||||
|
],
|
||||||
|
files: [
|
||||||
|
(path: RelativePath("{{ name }}"), contents: .static("{{ contentName }}")),
|
||||||
|
(path: RelativePath("{{ directoryName }}/{{ fileName }}"), contents: .static("bContent")),
|
||||||
|
],
|
||||||
|
directories: [RelativePath("{{ directoryName }}")])
|
||||||
|
}
|
||||||
|
let name = "test name"
|
||||||
|
let contentName = "test content"
|
||||||
|
let directoryName = "test directory"
|
||||||
|
let fileName = "test file"
|
||||||
|
let destinationPath = try temporaryPath()
|
||||||
|
let expectedFiles: [(AbsolutePath, String)] = [
|
||||||
|
(destinationPath.appending(component: name), contentName),
|
||||||
|
(destinationPath.appending(components: directoryName, fileName), "bContent")
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
try subject.generate(at: try temporaryPath(),
|
||||||
|
to: destinationPath,
|
||||||
|
attributes: [
|
||||||
|
"--name", name,
|
||||||
|
"--contentName", contentName,
|
||||||
|
"--directoryName", directoryName,
|
||||||
|
"--fileName", fileName
|
||||||
|
])
|
||||||
|
|
||||||
|
// Then
|
||||||
|
try expectedFiles.forEach {
|
||||||
|
XCTAssertEqual(try FileHandler.shared.readTextFile($0.0), $0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_generated_files() throws {
|
||||||
|
// Given
|
||||||
|
let sourcePath = try temporaryPath()
|
||||||
|
let destinationPath = try temporaryPath()
|
||||||
|
let name = "test name"
|
||||||
|
let aContent = "test a content"
|
||||||
|
let bContent = "test b content"
|
||||||
|
templateLoader.loadTemplateStub = { _ in
|
||||||
|
Template(description: "",
|
||||||
|
attributes: [.required("name")],
|
||||||
|
files: [
|
||||||
|
(path: RelativePath("a"), contents: .generated(sourcePath.appending(component: "a"))),
|
||||||
|
(path: RelativePath("b/{{ name }}"), contents: .generated(sourcePath.appending(components: "b")))
|
||||||
|
],
|
||||||
|
directories: [RelativePath("b")])
|
||||||
|
}
|
||||||
|
templateLoader.loadGenerateFileStub = { filePath, _ in
|
||||||
|
if filePath == destinationPath.appending(components: "a") {
|
||||||
|
return aContent
|
||||||
|
} else if filePath == destinationPath.appending(components: "b") {
|
||||||
|
return bContent
|
||||||
|
} else {
|
||||||
|
XCTFail("unexpected path \(filePath)")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let expectedFiles: [(AbsolutePath, String)] = [
|
||||||
|
(destinationPath.appending(component: "a"), aContent),
|
||||||
|
(destinationPath.appending(components: "b", name), bContent)
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
try subject.generate(at: sourcePath,
|
||||||
|
to: destinationPath,
|
||||||
|
attributes: ["--name", name])
|
||||||
|
|
||||||
|
// Then
|
||||||
|
try expectedFiles.forEach {
|
||||||
|
XCTAssertEqual(try FileHandler.shared.readTextFile($0.0), $0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_attributes_passed_to_generated_file() throws {
|
||||||
|
// Given
|
||||||
|
let sourcePath = try temporaryPath()
|
||||||
|
let destinationPath = try temporaryPath()
|
||||||
|
var parsedAttributes: [ParsedAttribute] = []
|
||||||
|
templateLoader.loadGenerateFileStub = { filePath, attributes in
|
||||||
|
if filePath == sourcePath.appending(component: "a") {
|
||||||
|
parsedAttributes = attributes
|
||||||
|
} else {
|
||||||
|
XCTFail("unexpected path \(filePath)")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
templateLoader.loadTemplateStub = { _ in
|
||||||
|
Template(description: "",
|
||||||
|
attributes: [.required("name")],
|
||||||
|
files: [(path: RelativePath("a"),
|
||||||
|
contents: .generated(sourcePath.appending(component: "a")))])
|
||||||
|
}
|
||||||
|
let expectedParsedAttributes: [ParsedAttribute] = [ParsedAttribute(name: "name", value: "attribute name")]
|
||||||
|
|
||||||
|
// When
|
||||||
|
try subject.generate(at: sourcePath,
|
||||||
|
to: destinationPath,
|
||||||
|
attributes: ["--name", "attribute name"])
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(parsedAttributes, expectedParsedAttributes)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,18 @@
|
||||||
import ProjectDescription
|
|
||||||
import TemplateDescription
|
import TemplateDescription
|
||||||
|
|
||||||
let nameArgument: Template.Attribute = .required("name")
|
let nameAttribute: Template.Attribute = .required("name")
|
||||||
let platformArgument: Template.Attribute = .optional("platform", default: "ios")
|
let platformAttribute: Template.Attribute = .optional("platform", default: "ios")
|
||||||
|
|
||||||
let testContents = """
|
let testContents = """
|
||||||
// this is test \(nameArgument) content
|
// this is test \(nameAttribute) content
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let template = Template(
|
let template = Template(
|
||||||
description: "Custom \(nameArgument)",
|
description: "Custom \(nameAttribute)",
|
||||||
arguments: [
|
attributes: [
|
||||||
nameArgument,
|
nameArgument,
|
||||||
platformArgument
|
platformAttribute
|
||||||
],
|
],
|
||||||
files: [
|
files: [
|
||||||
.static(path: "custom_dir/custom.swift",
|
.static(path: "custom_dir/custom.swift",
|
||||||
|
|
Loading…
Reference in New Issue