-
-
Notifications
You must be signed in to change notification settings - Fork 160
Update the generator to swift 6.2 #881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Sencudra
wants to merge
14
commits into
exercism:main
Choose a base branch
from
Sencudra:support_swift_6_2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 13 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
af9c0fa
Add tests for track config
Sencudra d3a0149
Add .vscode to .gitignore
Sencudra d3b833e
Add toml tests
Sencudra 0e7bcd8
Rename generator-plugins to Stencil plugins
Sencudra 50b202d
Add canonical data tests
Sencudra 0a0d96c
Add exercise config and tests
Sencudra e131c4f
Some adjustements
Sencudra f5c850e
Adjust test script
Sencudra 2b0baa9
Adjust readme with new details
Sencudra 6d1f4cd
Merge branch 'main' into support_swift_6_2
Sencudra 1f0a22f
Fix building
Sencudra 6ff7135
Upgrade package to 6.2. Fix bug reading json.
Sencudra ce3802c
Rename test-generator.sh
Sencudra 3d03f27
Review fixes: Add trailing lines
Sencudra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,34 @@ | ||
| // swift-tools-version:5.7 | ||
| // The swift-tools-version declares the minimum version of Swift required to build this package. | ||
| // swift-tools-version:6.2 | ||
|
|
||
| import PackageDescription | ||
|
|
||
| let package = Package( | ||
| name: "Generator", | ||
| platforms: [ | ||
| .macOS(.v12) // Set the minimum macOS version to 10.15 or any version greater than 10.15. | ||
| ], | ||
| dependencies: [ | ||
| .package(url: "https://github.com/stencilproject/Stencil.git", from: "0.15.1"), | ||
| .package(url: "https://github.com/LebJe/TOMLKit.git", from: "0.5.5"), | ||
| .package(url: "https://github.com/apple/swift-format", from: "600.0.0"), | ||
| ], | ||
| targets: [ | ||
| // Targets are the basic building blocks of a package. A target can define a module or a test suite. | ||
| // Targets can depend on other targets in this package, and on products in packages which this package depends on. | ||
| .target( | ||
| name: "Generator", | ||
| dependencies: [ | ||
| .product(name: "Stencil", package: "Stencil"), | ||
| .product(name: "TOMLKit", package: "TOMLKit"), | ||
| .product(name: "SwiftFormat", package: "swift-format"), | ||
| ]), | ||
| .testTarget( | ||
| name: "GeneratorTests", | ||
| dependencies: ["Generator"]), | ||
| ] | ||
| name: "Generator", | ||
| platforms: [ | ||
| .macOS(.v13) // macOS 13.0 (Ventura) or later. | ||
| ], | ||
| dependencies: [ | ||
| .package(url: "https://github.com/stencilproject/Stencil.git", from: "0.15.1"), | ||
| .package(url: "https://github.com/LebJe/TOMLKit.git", from: "0.6.0"), | ||
| .package(url: "https://github.com/apple/swift-format", from: "602.0.0"), | ||
| .package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.1"), | ||
| ], | ||
| targets: [ | ||
| .executableTarget( | ||
| name: "Generator", | ||
| dependencies: [ | ||
| .product(name: "Stencil", package: "Stencil"), | ||
| .product(name: "TOMLKit", package: "TOMLKit"), | ||
| .product(name: "SwiftFormat", package: "swift-format"), | ||
| .product(name: "ArgumentParser", package: "swift-argument-parser") | ||
| ] | ||
| ), | ||
| .testTarget( | ||
| name: "GeneratorTests", | ||
| dependencies: ["Generator"], | ||
| resources: [ | ||
| .copy("Resources") | ||
| ] | ||
| ), | ||
| ] | ||
| ) |
21 changes: 21 additions & 0 deletions
21
generator/Sources/Generator/Extensions/URL+Extensions.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import Foundation | ||
|
|
||
| extension URL { | ||
|
|
||
| func validateFileExists() throws { | ||
| var isDirectory: ObjCBool = true | ||
| let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) | ||
| guard exists && !isDirectory.boolValue else { | ||
| throw GeneratorError.noFile("No such file: \(path)") | ||
| } | ||
| } | ||
|
|
||
| func validateDirectoryExists() throws { | ||
| var isDirectory: ObjCBool = true | ||
| let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) | ||
| guard exists && isDirectory.boolValue else { | ||
| throw GeneratorError.noDirectory("No such directory: \(path)") | ||
| } | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| import Foundation | ||
| import ArgumentParser | ||
| import SwiftFormat | ||
|
|
||
| enum GeneratorError: Error, Equatable { | ||
| case noDirectory(String) | ||
| case noFile(String) | ||
| case noExercise(String) | ||
| case remoteError(String) | ||
| case internalError(String) | ||
| } | ||
|
|
||
| enum ExerciseKind: String, CaseIterable, ExpressibleByArgument { | ||
| case practice | ||
| case concept | ||
| } | ||
|
|
||
| @main | ||
| struct Generator: AsyncParsableCommand { | ||
|
|
||
| // MARK: - Static Properties | ||
|
|
||
| static let configuration = CommandConfiguration( | ||
| commandName: "Generator", | ||
| abstract: "A Swift track tool for generating Swift test files for an exercise." | ||
| ) | ||
|
|
||
| // MARK: - Arguments | ||
|
|
||
| @Argument( | ||
| help: "The slug of the exercise to process." | ||
| ) | ||
| var exerciseSlug: String | ||
|
|
||
| @Option( | ||
| name: [.short, .long], | ||
| help: "The kind of exercise to process. Possible values: \(ExerciseKind.allCases.map { $0.rawValue }.joined(separator: ", "))" | ||
| ) | ||
| var exerciseKind: ExerciseKind = .practice | ||
|
|
||
| @Option( | ||
| help: """ | ||
| The absolute or relative path to the exercise within the track directory. | ||
| Will use exercise kind and track path to calculate if not specified. | ||
| """, | ||
| transform: { URL(filePath: $0).standardizedFileURL } | ||
| ) | ||
| var exercisePath: URL? | ||
|
|
||
| @Option( | ||
| help: "The absolute or relative path to the track directory. Defaults to the current directory.", | ||
| transform: { URL(filePath: $0).standardizedFileURL } | ||
| ) | ||
| var trackDirectoryPath: URL = URL(filePath: FileManager.default.currentDirectoryPath) | ||
|
|
||
| // MARK: - Private Properties | ||
|
|
||
| private lazy var exerciseDirectoryURL: URL = { | ||
| guard let exercisePath else { | ||
| return trackDirectoryPath.appending(components: "exercises", "\(exerciseKind.rawValue)", "\(exerciseSlug)") | ||
| } | ||
| return exercisePath | ||
| }() | ||
|
|
||
| private lazy var trackConfigURL: URL = { trackDirectoryPath.appending(components: "config.json") }() | ||
| private lazy var exerciseConfigURL: URL = { exerciseDirectoryURL.appending(components: ".meta", "config.json") }() | ||
| private lazy var templateURL: URL = { exerciseDirectoryURL.appending(components: ".meta", "template.swift") }() | ||
| private lazy var tomlURL: URL = { exerciseDirectoryURL.appending(components: ".meta", "tests.toml") }() | ||
|
|
||
| // MARK: - Internal Methods | ||
|
|
||
| mutating func validate() throws { | ||
| try trackDirectoryPath.validateDirectoryExists() | ||
| try trackConfigURL.validateFileExists() | ||
|
|
||
| try exerciseDirectoryURL.validateDirectoryExists() | ||
| try exerciseConfigURL.validateFileExists() | ||
| try templateURL.validateFileExists() | ||
| try tomlURL.validateFileExists() | ||
|
|
||
| try validateExerciseExist() | ||
| } | ||
|
|
||
| mutating func run() async throws { | ||
| let context = try await makeTestContext() | ||
| try generateTestFile(context: context) | ||
| } | ||
|
|
||
| // MARK: - Private Methods | ||
|
|
||
| private mutating func validateExerciseExist() throws { | ||
| let trackConfig = try TrackConfig(from: trackConfigURL) | ||
| guard trackConfig.checkExistance(slug: exerciseSlug, kind: exerciseKind) else { | ||
| throw GeneratorError.noExercise("No exercise found for \(exerciseSlug) in \(trackConfigURL.path)") | ||
| } | ||
| } | ||
|
|
||
| private mutating func makeTestContext() async throws -> [String: Any] { | ||
| var canonicalData = try await CanonicalData.fetch(slug: exerciseSlug) | ||
| let toml = try TOMLConfig(from: tomlURL) | ||
| canonicalData.whitelistTests(withUUIDs: toml.uuids) | ||
| return canonicalData.context | ||
| } | ||
|
|
||
| private mutating func generateTestFile(context: [String: Any]) throws { | ||
| let renderedString = try Stencil.render(template: templateURL, context: context) // canonicalData.jsonData) | ||
|
|
||
| let testTargetURL = try getTargetTestFileURL() | ||
|
|
||
| var outputText = "" | ||
| let configuration = Configuration() | ||
| let swiftFormat = SwiftFormatter(configuration: configuration) | ||
| try swiftFormat.format( | ||
| source: renderedString, | ||
| assumingFileURL: nil, | ||
| selection: .infinite, | ||
| to: &outputText | ||
| ) | ||
| try outputText.write(toFile: testTargetURL.path, atomically: true, encoding: .utf8) | ||
| } | ||
|
|
||
| private mutating func getTargetTestFileURL() throws -> URL { | ||
| let config = try ExerciseConfig(from: exerciseConfigURL) | ||
| let testFile = exerciseDirectoryURL.appending(component: try config.getTargetTestFileURL()) | ||
| return testFile | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import Foundation | ||
| #if canImport(FoundationNetworking) | ||
| import FoundationNetworking | ||
| #endif | ||
|
|
||
| struct CanonicalData { | ||
|
|
||
| var context: [String: Any] | ||
|
|
||
| init(dictionary: [String: Any]) { | ||
| self.context = dictionary | ||
| } | ||
|
|
||
| mutating func whitelistTests(withUUIDs uuidsToKeep: Set<String>) { | ||
| if let cases = context["cases"] as? [[String: Any]] { | ||
| context["cases"] = whitelistTests(from: cases, withUUIDs: uuidsToKeep) | ||
| } | ||
| } | ||
|
|
||
| private func whitelistTests(from cases: [[String: Any]], withUUIDs uuidsToKeep: Set<String>) -> [[String: Any]] { | ||
| return cases.compactMap { caseData in | ||
| var caseData = caseData | ||
|
|
||
| if let uuid = caseData["uuid"] as? String { | ||
| return uuidsToKeep.contains(uuid) ? caseData : nil | ||
| } else if let nestedCases = caseData["cases"] as? [[String: Any]] { | ||
| let nestedWhitelisted = whitelistTests(from: nestedCases, withUUIDs: uuidsToKeep) | ||
| if !nestedWhitelisted.isEmpty { | ||
| caseData["cases"] = nestedWhitelisted | ||
| return caseData | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| extension CanonicalData { | ||
|
|
||
| static func fetch(slug: String) async throws -> CanonicalData { | ||
| print("Loading canonical data for \"\(slug)\"...", terminator: "") | ||
| let urlString = "https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/\(slug)/canonical-data.json" | ||
| guard let url = URL(string: urlString) else { | ||
| throw GeneratorError.remoteError("Invalid URL for exercise \(slug)") | ||
| } | ||
|
|
||
| let (data, response) = try await URLSession.shared.data(from: url) | ||
| guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { | ||
| throw GeneratorError.remoteError("HTTP error with code: \((response as? HTTPURLResponse)?.statusCode ?? -1)") | ||
| } | ||
| print("OK!") | ||
| guard let jsonData = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { | ||
| throw GeneratorError.remoteError("Invalid canonical data format") | ||
| } | ||
|
|
||
| return .init(dictionary: jsonData) | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import Foundation | ||
|
|
||
| protocol Config: Decodable { | ||
|
|
||
| init(from url: URL) throws | ||
| init(from string: String) throws | ||
|
|
||
| } | ||
|
|
||
| extension Config { | ||
|
|
||
| init(from url: URL) throws { | ||
| try self.init(data: Data(contentsOf: url)) | ||
| } | ||
|
|
||
| init(from string: String) throws { | ||
| try self.init(data: Data(string.utf8)) | ||
| } | ||
|
|
||
| private init(data: Data) throws { | ||
| self = try JSONDecoder().decode(Self.self, from: data) | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import Foundation | ||
|
|
||
| struct ExerciseConfig: Config { | ||
|
|
||
| struct Files: Codable { | ||
| let solution: [String] | ||
| let test: [String] | ||
| let example: [String] | ||
| } | ||
|
|
||
| let files: Files | ||
|
|
||
| func getTargetTestFileURL() throws -> String { | ||
| if files.test.isEmpty { | ||
| throw GeneratorError.internalError("Exercise config file has an unexpected format or no test files are defined.") | ||
| } | ||
| return files.test.first! | ||
| } | ||
|
|
||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.