import Foundation struct SwiftCodeGenerator { private let fileContentReader: FileContentReader private let indentationUnit: String init( fileContentReader: FileContentReader = FileContentReader(), indentationUnit: String = " " ) { self.fileContentReader = fileContentReader self.indentationUnit = indentationUnit } func generate(from rootNamespace: NamespaceNode) throws -> String { generatedFileBanner() + (try renderNamespace(rootNamespace, depth: 0)) } } private extension SwiftCodeGenerator { func generatedFileBanner() -> String { """ // Generated by the Embedder plugin from the "Static Inline" directory. // Any manual changes will be overwritten on the next build. """ + "\n" } func renderNamespace(_ namespace: NamespaceNode, depth: Int) throws -> String { let openingLine = namespaceOpeningLine(for: namespace, depth: depth) let bodyLines = try renderBody(of: namespace, depth: depth + 1) let closingLine = namespaceClosingLine(depth: depth) return openingLine + bodyLines + closingLine } func namespaceOpeningLine(for namespace: NamespaceNode, depth: Int) -> String { "\(indent(depth: depth))enum \(namespace.name) {\n" } func namespaceClosingLine(depth: Int) -> String { "\(indent(depth: depth))}\n" } func renderBody(of namespace: NamespaceNode, depth: Int) throws -> String { let fileDeclarations = try namespace.files.map { try renderFileDeclaration($0, depth: depth) } let nestedNamespaces = try namespace.subNamespaces.map { try renderNamespace($0, depth: depth) } return joinWithBlankLines(fileDeclarations + nestedNamespaces) } func renderFileDeclaration(_ file: EmbeddableFile, depth: Int) throws -> String { let propertyName = IdentifierSanitizer.propertyName(fromFilename: file.filename) let literal = try renderStringLiteral(for: file, baseIndent: indent(depth: depth)) return "\(indent(depth: depth))static let \(propertyName): String = \(literal)\n" } func renderStringLiteral(for file: EmbeddableFile, baseIndent: String) throws -> String { let content = try fileContentReader.readTextContent(of: file) let rawLiteral = StringLiteralEscaper.rawTripleQuotedLiteral(from: content) return indentContinuationLines(of: rawLiteral, by: baseIndent) } func indentContinuationLines(of literal: String, by baseIndent: String) -> String { let lines = literal.components(separatedBy: "\n") guard lines.count > 1 else { return literal } let firstLine = lines[0] let continuationLines = lines.dropFirst().map { "\(baseIndent)\($0)" } return ([firstLine] + continuationLines).joined(separator: "\n") } func joinWithBlankLines(_ declarations: [String]) -> String { declarations.joined(separator: "\n") } func indent(depth: Int) -> String { String(repeating: indentationUnit, count: depth) } }