From b6b0477e7a063a734cdc253fbd9cff42c895be40 Mon Sep 17 00:00:00 2001 From: Andrey Frolov Date: Thu, 13 Feb 2025 14:23:26 +0100 Subject: [PATCH] support environment variables and update libs --- Package.resolved | 38 +++++++++++----- Package.swift | 4 +- .../CodeGenerator/Models/PropertyModel.swift | 5 ++- .../Stages/GenerationStage/Template.swift | 8 +++- .../DefaultTemplateFiller.swift | 6 ++- .../TreeParserStage/TreeParser/Resolver.swift | 6 ++- Sources/Common/Extensions/String.swift | 11 ++++- .../Builders/AnySchemaBuilder.swift | 11 ++++- Sources/GASTTree/PropertyNode.swift | 5 ++- .../BuildCodeGeneratorPipelineFactory.swift | 2 + .../CLI/Generation/GenerationCommand.swift | 1 + .../CLI/Generation/GenerationConfig.swift | 1 + .../CodeGen/ServiceGenerationStage.swift | 32 +++++++++++-- ...tubBuildCodeGeneratorPipelineFactory.swift | 2 + .../EndToEndTests/DataGenerationTest.swift | 1 + .../EndToEndTests/EndToEndTests.swift | 1 + .../EndToEndTests/TestTemplates.swift | 45 ++++++++++++------- .../RealDataTests/RealDataTests.swift | 1 + 18 files changed, 141 insertions(+), 39 deletions(-) diff --git a/Package.resolved b/Package.resolved index 7ae47a98..699dc90c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/shibapm/Komondor.git", "state": { "branch": null, - "revision": "7c3c6040c01c99b83afc7eaa8f2a8a69337f659a", - "version": "1.1.1" + "revision": "90b087b1e39069684b1ff4bf915c2aae594f2d60", + "version": "1.1.3" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/shibapm/PackageConfig.git", "state": { "branch": null, - "revision": "84d4d70de31a797ca54abee47816cdafba36d290", - "version": "1.0.2" + "revision": "58523193c26fb821ed1720dcd8a21009055c7cdb", + "version": "1.1.3" } }, { @@ -37,6 +37,15 @@ "version": "1.0.1" } }, + { + "package": "Rainbow", + "repositoryURL": "https://github.com/onevcat/Rainbow.git", + "state": { + "branch": null, + "revision": "626c3d4b6b55354b4af3aa309f998fae9b31a3d9", + "version": "3.2.0" + } + }, { "package": "ShellOut", "repositoryURL": "https://github.com/JohnSundell/ShellOut.git", @@ -60,8 +69,8 @@ "repositoryURL": "https://github.com/stencilproject/Stencil", "state": { "branch": null, - "revision": "973e190edf5d09274e4a6bc2e636c86899ed84c3", - "version": "0.14.1" + "revision": "4f222ac85d673f35df29962fc4c36ccfdaf9da5b", + "version": "0.15.1" } }, { @@ -69,16 +78,16 @@ "repositoryURL": "https://github.com/SwiftGen/StencilSwiftKit.git", "state": { "branch": null, - "revision": "54cbedcdbb4334e03930adcff7343ffaf317bf0f", - "version": "2.8.0" + "revision": "20e2de5322c83df005939d9d9300fab130b49f97", + "version": "2.10.1" } }, { "package": "SwagGen", - "repositoryURL": "https://github.com/LastSprint/SwagGen", + "repositoryURL": "https://github.com/surfstudio/surf-SwagGen", "state": { "branch": null, - "revision": "285ee89b027344520cbda903ea1c74bf0f9baac8", + "revision": "c10461f6eb74d8c834e7621e82068a5d938be27d", "version": null } }, @@ -90,6 +99,15 @@ "revision": "2e949055d9797c1a6bddcda0e58dada16cc8e970", "version": "6.0.3" } + }, + { + "package": "Yams", + "repositoryURL": "https://github.com/jpsim/Yams.git", + "state": { + "branch": null, + "revision": "b08dba4bcea978bf1ad37703a384097d3efce5af", + "version": "1.0.2" + } } ] }, diff --git a/Package.swift b/Package.swift index 91786618..a7f1b2c6 100644 --- a/Package.swift +++ b/Package.swift @@ -29,8 +29,8 @@ var testTargets: [Target] = [ var dependencies: [PackageDescription.Package.Dependency] = [ // because SPM cant resolve it by their own ((((: // .package(url: "https://github.com/kylef/PathKit.git", from: "0.9.0"), - .package(url: "https://github.com/LastSprint/SwagGen", .revision("285ee89b027344520cbda903ea1c74bf0f9baac8")), - .package(url: "https://github.com/stencilproject/Stencil", from: "0.14.1"), + .package(url: "https://github.com/surfstudio/surf-SwagGen", .revision("c10461f6eb74d8c834e7621e82068a5d938be27d")), + .package(url: "https://github.com/stencilproject/Stencil", from: "0.15.1"), .package(url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.3"), .package(url: "https://github.com/onevcat/Rainbow", from: "3.1.5"), .package(url: "https://github.com/jpsim/Yams", from: "1.0.0"), diff --git a/Sources/CodeGenerator/Models/PropertyModel.swift b/Sources/CodeGenerator/Models/PropertyModel.swift index 42e8bc3f..ddf0207a 100644 --- a/Sources/CodeGenerator/Models/PropertyModel.swift +++ b/Sources/CodeGenerator/Models/PropertyModel.swift @@ -79,6 +79,7 @@ public struct PropertyModel { public let description: String? public let type: PossibleType public let isNullable: Bool + public let pattern: String? /// This value will be used as type for generation public let typeModel: ItemTypeModel @@ -86,7 +87,8 @@ public struct PropertyModel { init(name: String, description: String?, type: PropertyModel.PossibleType, - isNullable: Bool) { + isNullable: Bool, + pattern: String?) { self.name = name self.description = description self.type = type @@ -96,6 +98,7 @@ public struct PropertyModel { isObject: type.isObject, enumTypeName: type.enumTypeName, aliasTypeName: type.aliasTypeName) + self.pattern = pattern } } diff --git a/Sources/CodeGenerator/Stages/GenerationStage/Template.swift b/Sources/CodeGenerator/Stages/GenerationStage/Template.swift index 84d42240..1bc07098 100644 --- a/Sources/CodeGenerator/Stages/GenerationStage/Template.swift +++ b/Sources/CodeGenerator/Stages/GenerationStage/Template.swift @@ -57,17 +57,23 @@ public struct Template: Decodable { /// Example: Path/To/Project/Models/{name} public let destinationPath: String + /// Environment where custom variables are stored + /// These variables can be used in a Stencil template. + public let environment: [String: String]? + public init(type: Template.TemplateType, nameSuffix: String?, fileExtension: String, fileNameCase: FileNameCase? = .camelCase, templatePath: String, - destinationPath: String) { + destinationPath: String, + environment: [String: String]?) { self.type = type self.nameSuffix = nameSuffix self.fileExtension = fileExtension self.fileNameCase = fileNameCase self.templatePath = templatePath self.destinationPath = destinationPath + self.environment = environment } } diff --git a/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift b/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift index 8d6a83ee..530c6bcc 100644 --- a/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift +++ b/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift @@ -71,7 +71,11 @@ public class DefaultTemplateFiller: TemplateFiller { templateExtension.registerStringFilter("sanitizeUrlPath") { $0.sanitizeUrlPath() } - + + templateExtension.registerStringFilter("withEscapedCharacters") { + $0.withEscapedCharacters() + } + return templateExtension } diff --git a/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift b/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift index f39163e4..4e1ca357 100644 --- a/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift +++ b/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift @@ -190,12 +190,14 @@ public class Resolver { return .init(name: property.name, description: property.description, type: .array(.init(name: "", itemsType: try arrayItemTypeUnwrapper(arr.itemsType))), - isNullable: property.nullable) + isNullable: property.nullable, + pattern: property.pattern) case .simple(let val): return .init(name: property.name, description: property.description, type: try propertyTypeUnwrapper(val), - isNullable: property.nullable) + isNullable: property.nullable, + pattern: property.pattern) } } diff --git a/Sources/Common/Extensions/String.swift b/Sources/Common/Extensions/String.swift index c95b5ad0..b0543e3c 100644 --- a/Sources/Common/Extensions/String.swift +++ b/Sources/Common/Extensions/String.swift @@ -119,7 +119,7 @@ extension String { public func replaceNameTemplate(with name: String) -> String { return self.replacingOccurrences(of: "\\{.*?\\}", with: name, options: .regularExpression) } - + /// Return package name from a full api file name. /// For example: /// self = "/users/username/swagger/products/api.yaml @@ -136,7 +136,14 @@ extension String { public func sanitizeUrlPath() -> String { return String(self.drop { $0 == "/" }) } - + + /// String with all escaped characters. + public func withEscapedCharacters() -> String { + return self.unicodeScalars + .map { $0.escaped(asASCII: true) } + .joined() + } + private func pathToCamelCase() -> String { return self .split(whereSeparator: { $0 == "/" || $0 == "_" }) diff --git a/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift b/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift index a3badf7c..5d25b976 100644 --- a/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift +++ b/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift @@ -171,12 +171,21 @@ public struct AnySchemaBuilder: SchemaBuilder { isNullable = property.schema.metadata.nullable } + var pattern: String? + + switch property.schema.type { + case .string(let stringSchema): + pattern = stringSchema.pattern + default: + break + } return PropertyNode(name: property.name, type: type, description: property.schema.metadata.description, example: property.schema.metadata.example, - nullable: isNullable) + nullable: isNullable, + pattern: pattern) } return SchemaModelNode(name: name, properties: properties, description: meta.description, apiDefinitionFileRef: apiDefinitionFileRef) diff --git a/Sources/GASTTree/PropertyNode.swift b/Sources/GASTTree/PropertyNode.swift index 2ec84c1b..e9d30922 100644 --- a/Sources/GASTTree/PropertyNode.swift +++ b/Sources/GASTTree/PropertyNode.swift @@ -19,17 +19,20 @@ public struct PropertyNode { public let description: String? public let example: Any? public let nullable: Bool + public let pattern: String? public init(name: String, type: PossibleType, description: String?, example: Any?, - nullable: Bool) { + nullable: Bool, + pattern: String?) { self.name = name self.type = type self.description = description self.example = example self.nullable = nullable + self.pattern = pattern } } diff --git a/Sources/Pipelines/BuldGASTTree/BuildCodeGeneratorPipelineFactory.swift b/Sources/Pipelines/BuldGASTTree/BuildCodeGeneratorPipelineFactory.swift index 84712669..fff34ca5 100644 --- a/Sources/Pipelines/BuldGASTTree/BuildCodeGeneratorPipelineFactory.swift +++ b/Sources/Pipelines/BuldGASTTree/BuildCodeGeneratorPipelineFactory.swift @@ -24,6 +24,7 @@ public struct BuildCodeGeneratorPipelineFactory { } public static func build(templates: [Template], + globalEnvironment: [String: String], specificationRootPath: String, astNodesToExclude: Set, serviceName: String, @@ -81,6 +82,7 @@ public struct BuildCodeGeneratorPipelineFactory { specificationRootPath: specificationRootPath, templateFiller: templateFiller, modelExtractor: modelExtractor, + globalEnvironment: globalEnvironment, prefixCutter: prefixCutter ).erase() ).erase(), diff --git a/Sources/Pipelines/CLI/Generation/GenerationCommand.swift b/Sources/Pipelines/CLI/Generation/GenerationCommand.swift index 1d3a6f43..cff4b30e 100644 --- a/Sources/Pipelines/CLI/Generation/GenerationCommand.swift +++ b/Sources/Pipelines/CLI/Generation/GenerationCommand.swift @@ -67,6 +67,7 @@ public class GenerationCommand: Command { let pipeline = BuildCodeGeneratorPipelineFactory.build( templates: config.templates, + globalEnvironment: config.environment ?? [:], specificationRootPath: config.specificationRootPath ?? "", astNodesToExclude: try Utils.Urls.makeAstNodeRefsAbsolute(refs: rawAstNodesToExclude), serviceName: serviceName, diff --git a/Sources/Pipelines/CLI/Generation/GenerationConfig.swift b/Sources/Pipelines/CLI/Generation/GenerationConfig.swift index 17d61770..f76939df 100644 --- a/Sources/Pipelines/CLI/Generation/GenerationConfig.swift +++ b/Sources/Pipelines/CLI/Generation/GenerationConfig.swift @@ -14,6 +14,7 @@ public struct AnalytcsConfig: Decodable { } public struct GenerationConfig: Decodable { + public var environment: [String: String]? public var templates: [Template] public var analytcsConfig: AnalytcsConfig? public var prefixesToCutDownInServiceNames: [String]? diff --git a/Sources/Pipelines/CodeGen/ServiceGenerationStage.swift b/Sources/Pipelines/CodeGen/ServiceGenerationStage.swift index 22dd80d0..949e0012 100644 --- a/Sources/Pipelines/CodeGen/ServiceGenerationStage.swift +++ b/Sources/Pipelines/CodeGen/ServiceGenerationStage.swift @@ -24,6 +24,7 @@ public struct ServiceGenerationStage: PipelineStage { var next: AnyPipelineStage<[SourceCode]> + private let globalEnvironment: [String: String] private let templates: [Template] private let serviceName: String private let prefixCutter: PrefixCutter? @@ -35,6 +36,7 @@ public struct ServiceGenerationStage: PipelineStage { specificationRootPath: String, templateFiller: TemplateFiller, modelExtractor: ModelExtractor, + globalEnvironment: [String: String], prefixCutter: PrefixCutter? = nil ) { self.next = next @@ -44,6 +46,7 @@ public struct ServiceGenerationStage: PipelineStage { self.templateFiller = templateFiller self.modelExtractor = modelExtractor self.prefixCutter = prefixCutter + self.globalEnvironment = globalEnvironment } public func run(with input: [[PathModel]]) throws { @@ -84,12 +87,14 @@ public struct ServiceGenerationStage: PipelineStage { templates.filter { $0.type == .service }, with: [ContextKeys.service: serviceGenerationModel], name: serviceGenerationModel.name, + serviceName: serviceName, apiDefinitionFileRef: serviceGenerationModel.apiDefinitionFileRef) let generatedModels = try objectModels.flatMap { return try fillTemplates(templates.filter { $0.type == .model }, with: [ContextKeys.model: $0], name: $0.name, + serviceName: serviceName, apiDefinitionFileRef: $0.apiDefinitionFileRef) } @@ -97,6 +102,7 @@ public struct ServiceGenerationStage: PipelineStage { return try fillTemplates(templates.filter { $0.type == .enum }, with: [ContextKeys.enum: $0], name: $0.name, + serviceName: serviceName, apiDefinitionFileRef: $0.apiDefinitionFileRef) } @@ -104,6 +110,7 @@ public struct ServiceGenerationStage: PipelineStage { return try fillTemplates(templates.filter { $0.type == .typealias }, with: [ContextKeys.model: $0], name: $0.name, + serviceName: serviceName, apiDefinitionFileRef: $0.apiDefinitionFileRef) } @@ -111,11 +118,18 @@ public struct ServiceGenerationStage: PipelineStage { } - private func fillTemplates(_ templates: [Template], with model: [String: Any], name: String, apiDefinitionFileRef: String) throws -> [SourceCode] { + private func fillTemplates( + _ templates: [Template], + with model: [String: Any], + name: String, + serviceName: String, + apiDefinitionFileRef: String + ) throws -> [SourceCode] { return try templates.map { template in + let context = makeContext(from: model, with: serviceName, in: template) let sourceCode = try wrap( - templateFiller.fillTemplate(at: template.templatePath, with: model), - message: "While filling template at \(template.templatePath) with model \(name)" + templateFiller.fillTemplate(at: template.templatePath, with: context), + message: "While filling template at \(template.templatePath) with model \(context)" ) return SourceCode(code: sourceCode, fileName: template.buildFileName(for: name), @@ -123,6 +137,18 @@ public struct ServiceGenerationStage: PipelineStage { apiDefinitionFileRef: apiDefinitionFileRef) } } + + private func makeContext( + from model: [String: Any], + with serviceName: String, + in template: Template + ) -> [String: Any] { + let templateEnvironment = template.environment ?? [:] + let environment = globalEnvironment.merging(templateEnvironment, uniquingKeysWith: { _, value in value }) + return model + .merging(environment, uniquingKeysWith: { value, _ in value }) + .merging(["serviceName": serviceName], uniquingKeysWith: { value, _ in value }) + } } private extension Template { diff --git a/Sources/UtilsForTesting/StubBuildCodeGeneratorPipelineFactory.swift b/Sources/UtilsForTesting/StubBuildCodeGeneratorPipelineFactory.swift index 1dd1a114..f499a266 100644 --- a/Sources/UtilsForTesting/StubBuildCodeGeneratorPipelineFactory.swift +++ b/Sources/UtilsForTesting/StubBuildCodeGeneratorPipelineFactory.swift @@ -77,6 +77,7 @@ public struct StubBuildCodeGeneratorPipelineFactory { } public static func build(templates: [Template], + globalEnvironment: [String: String], specificationRootPath: String, astNodesToExclude: Set, serviceName: String, @@ -132,6 +133,7 @@ public struct StubBuildCodeGeneratorPipelineFactory { specificationRootPath: specificationRootPath, templateFiller: templateFiller, modelExtractor: modelExtractor, + globalEnvironment: globalEnvironment, prefixCutter: prefixCutter ).erase() ).erase(), diff --git a/Tests/PipelinesTests/EndToEndTests/DataGenerationTest.swift b/Tests/PipelinesTests/EndToEndTests/DataGenerationTest.swift index ae975a4e..25e614a0 100644 --- a/Tests/PipelinesTests/EndToEndTests/DataGenerationTest.swift +++ b/Tests/PipelinesTests/EndToEndTests/DataGenerationTest.swift @@ -147,6 +147,7 @@ class DataGenerationTest: XCTestCase { try StubBuildCodeGeneratorPipelineFactory.build( templates: TestTemplates.swiftTemplateModels, + globalEnvironment: [:], specificationRootPath: homePath, astNodesToExclude: [], serviceName: "PackageSeparation", diff --git a/Tests/PipelinesTests/EndToEndTests/EndToEndTests.swift b/Tests/PipelinesTests/EndToEndTests/EndToEndTests.swift index 7f474583..cad0e349 100644 --- a/Tests/PipelinesTests/EndToEndTests/EndToEndTests.swift +++ b/Tests/PipelinesTests/EndToEndTests/EndToEndTests.swift @@ -105,6 +105,7 @@ class EndToEndTests: XCTestCase { try BuildCodeGeneratorPipelineFactory .build(templates: templates, + globalEnvironment: [:], specificationRootPath: "", astNodesToExclude: [], serviceName: serviceName, diff --git a/Tests/PipelinesTests/EndToEndTests/TestTemplates.swift b/Tests/PipelinesTests/EndToEndTests/TestTemplates.swift index 81d9197d..464990ed 100644 --- a/Tests/PipelinesTests/EndToEndTests/TestTemplates.swift +++ b/Tests/PipelinesTests/EndToEndTests/TestTemplates.swift @@ -43,42 +43,50 @@ public struct TestTemplates { nameSuffix: "Api", fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Api.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .enum, nameSuffix: nil, fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Enum.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .model, nameSuffix: "Entry", fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Entry.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .model, nameSuffix: "Entity", fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Entity.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .service, nameSuffix: "Module", fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Module.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .service, nameSuffix: "Repository", fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Repository.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .service, nameSuffix: "Interactor", fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Interactor.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .service, nameSuffix: "Urls", fileExtension: "txt", templatePath: kotlinBaseTemplatePath + "/Urls.stencil", - destinationPath: testOutputPath) + destinationPath: testOutputPath, + environment: nil) ] } @@ -88,37 +96,44 @@ public struct TestTemplates { nameSuffix: "UrlRoute", fileExtension: "txt", templatePath: swiftBaseTemplatePath + "/UrlRoute.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .service, nameSuffix: "Service", fileExtension: "txt", templatePath: swiftBaseTemplatePath + "/Service.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .service, nameSuffix: "NetworkService", fileExtension: "txt", templatePath: swiftBaseTemplatePath + "/NetworkService.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .model, nameSuffix: "Entry", fileExtension: "txt", templatePath: swiftBaseTemplatePath + "/Entry.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .model, nameSuffix: "Entity", fileExtension: "txt", templatePath: swiftBaseTemplatePath + "/Entity.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .enum, nameSuffix: nil, fileExtension: "txt", templatePath: swiftBaseTemplatePath + "/Enum.stencil", - destinationPath: testOutputPath), + destinationPath: testOutputPath, + environment: nil), Template(type: .typealias, nameSuffix: nil, fileExtension: "txt", templatePath: swiftBaseTemplatePath + "/Typealias.stencil", - destinationPath: testOutputPath) + destinationPath: testOutputPath, + environment: nil) ] } } diff --git a/Tests/PipelinesTests/RealDataTests/RealDataTests.swift b/Tests/PipelinesTests/RealDataTests/RealDataTests.swift index b8bcf756..cf4babcd 100644 --- a/Tests/PipelinesTests/RealDataTests/RealDataTests.swift +++ b/Tests/PipelinesTests/RealDataTests/RealDataTests.swift @@ -46,6 +46,7 @@ final class RealDataTests: XCTestCase { do { _ = try BuildCodeGeneratorPipelineFactory.build( templates: [], + globalEnvironment: [:], specificationRootPath: "", astNodesToExclude: [], serviceName: "",