Skip to content

Commit e310129

Browse files
Rename
1 parent 923eaf1 commit e310129

File tree

7 files changed

+214
-5
lines changed

7 files changed

+214
-5
lines changed

Sources/AtCoderLibrary/Command/New.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ public struct New: ParsableCommand {
2727
FileManager.default.changeCurrentDirectoryPath(contestName)
2828

2929
let alphabets = problems.map(\.context.alphabet).map(Character.init)
30-
try PackageSwift(contestName: contestName, alphabets: alphabets).write()
31-
try Readme(contest: contest, problems: problems).write()
30+
try PackageSwift(contestName: contestName, alphabets: alphabets).codeGenerate()
31+
try Readme(contest: contest, problems: problems).codeGenerate()
3232
try problems.forEach {
33-
try Source(problem: $0).write()
33+
try Source(problem: $0).codeGenerate()
3434
}
3535
try problems.forEach {
36-
try Test(problem: $0).write()
36+
try Test(problem: $0).codeGenerate()
3737
}
38-
try TestLibrary().write()
38+
try TestLibrary().codeGenerate()
3939

4040
if open {
4141
SwiftShell.run("cd", contestName)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Foundation
2+
3+
protocol CodeTemplate {
4+
var fileName: String { get }
5+
var directory: String? { get }
6+
var source: String { get }
7+
}
8+
9+
extension CodeTemplate {
10+
func codeGenerate() throws {
11+
if let directory = directory {
12+
try FileManager.default.createDirectory(atPath: directory, withIntermediateDirectories: true)
13+
}
14+
let filePath = "\(directory ?? ".")/\(fileName)"
15+
guard !FileManager.default.fileExists(atPath: filePath) else {
16+
print("Skip file creation because the file exists.", "path: ", filePath)
17+
return
18+
}
19+
try source.write(toFile: filePath, atomically: true, encoding: .utf8)
20+
}
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
struct PackageSwift: CodeTemplate {
2+
let contestName: String
3+
let alphabets: [Character]
4+
let fileName = "Package.swift"
5+
let directory: String? = nil
6+
var source: String {
7+
"""
8+
// swift-tools-version:5.3
9+
import PackageDescription
10+
11+
let package = Package(
12+
name: "\(contestName.uppercased())",
13+
dependencies: [],
14+
targets: [
15+
\(alphabets.sorted()
16+
.map {
17+
"""
18+
.target(name: "\($0)"),
19+
.testTarget(name: "\($0)Tests", dependencies: ["\($0)", "TestLibrary"]),
20+
"""
21+
}
22+
.joined(separator: "\n"))
23+
.target(name: "TestLibrary", path: "Tests/TestLibrary"),
24+
]
25+
)
26+
"""
27+
}
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
struct Readme: CodeTemplate {
2+
let contest: Contest
3+
let problems: [Problem]
4+
let fileName = "README.md"
5+
let directory: String? = nil
6+
var source: String {
7+
let problems = self.problems.sorted(by: {
8+
$0.context.alphabet < $1.context.alphabet
9+
})
10+
return """
11+
# [\(contest.name)](\(contest.url))
12+
13+
問題名 | 実行時間制限 | メモリ制限
14+
:-- | --: | --:
15+
\(problems.map { problem in """
16+
[\(problem.context.alphabet) \(problem.name)](\(problem.url)) | \(Double(problem.timeLimit)/1000) sec | \(problem.memoryLimit) MB
17+
"""}.joined(separator: "\n"))
18+
19+
"""
20+
}
21+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
struct Source: CodeTemplate {
2+
let problem: Problem
3+
let fileName = "main.swift"
4+
var directory: String? { "Sources/\(problem.context.alphabet)" }
5+
var source: String {
6+
"""
7+
// \(problem.context.alphabet) - \(problem.name)
8+
// \(problem.url)
9+
// 実行制限時間: \(Double(problem.timeLimit)/1000) sec
10+
import Foundation
11+
12+
"""
13+
}
14+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
struct Test: CodeTemplate {
2+
let problem: Problem
3+
var className: String {
4+
if Int(problem.context.alphabet) != nil {
5+
// [!] Class name can not start with a number.
6+
return "_\(problem.context.alphabet)Tests"
7+
}
8+
return "\(problem.context.alphabet)Tests"
9+
}
10+
var fileName: String { "\(problem.context.alphabet)Tests.swift" }
11+
var directory: String? { "Tests/\(problem.context.alphabet)Tests" }
12+
var source: String {
13+
"""
14+
import XCTest
15+
import TestLibrary
16+
17+
let cases: [TestCase] = [
18+
\(problem.tests.map(Self.testCase).joined(separator: "\n"))
19+
]
20+
21+
final class \(className): XCTestCase, TimeLimit {
22+
let timeLimit: TimeInterval = \(Double(problem.timeLimit)/1000)
23+
24+
func testExample() throws {
25+
try cases.forEach(solve)
26+
}
27+
}
28+
"""
29+
}
30+
31+
private static func testCase(_ test: Problem.Test) -> String {
32+
let inputs = test.input.split(separator: "\n")
33+
let outputs = test.output.split(separator: "\n")
34+
return """
35+
(#filePath, #line,
36+
\"""
37+
\(inputs.map { "\($0)" }.joined(separator: "\n"))
38+
\""", \"""
39+
\(outputs.map { "\($0)" }.joined(separator: "\n"))
40+
\"""),
41+
"""
42+
}
43+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
struct TestLibrary: CodeTemplate {
2+
let directory: String? = "Tests/TestLibrary"
3+
let fileName = "TestLibrary.swift"
4+
var source: String {
5+
"""
6+
import XCTest
7+
import class Foundation.Bundle
8+
9+
public protocol TimeLimit {
10+
var timeLimit: TimeInterval { get }
11+
}
12+
13+
public typealias TestCase = (file: StaticString, line: UInt, input: String, expected: String)
14+
15+
public extension TimeLimit where Self: XCTestCase {
16+
func solve(file: StaticString, line: UInt, input: String, expected: String) throws {
17+
// Some of the APIs that we use below are available in macOS 10.13 and above.
18+
guard #available(macOS 10.13, *) else {
19+
return
20+
}
21+
let expected = expected.last == "\\n" ? expected : expected + "\\n"
22+
var error = ""
23+
let exp = expectation(description: "")
24+
let testTarget = String(describing: type(of: self))
25+
.replacingOccurrences(of: "Tests", with: "")
26+
.replacingOccurrences(of: "_", with: "")
27+
let binary = productsDirectory.appendingPathComponent(testTarget)
28+
let process = Process()
29+
30+
addTeardownBlock {
31+
if process.isRunning {
32+
process.terminate()
33+
}
34+
}
35+
36+
process.executableURL = binary
37+
let pipeInput = Pipe()
38+
process.standardInput = pipeInput
39+
let pipeOutput = Pipe()
40+
process.standardOutput = pipeOutput
41+
let pipeError = Pipe()
42+
process.standardError = pipeError
43+
44+
DispatchQueue.global().async {
45+
do {
46+
try process.run()
47+
pipeInput.fileHandleForWriting.write(input.data(using: .utf8)!)
48+
pipeInput.fileHandleForWriting.closeFile()
49+
process.waitUntilExit()
50+
exp.fulfill()
51+
error = String(data: pipeError.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
52+
} catch (let e) {
53+
error = e.localizedDescription
54+
}
55+
}
56+
let result = XCTWaiter.wait(for: [exp], timeout: timeLimit)
57+
switch result {
58+
case .completed:
59+
if error.isEmpty {
60+
let data = pipeOutput.fileHandleForReading.readDataToEndOfFile()
61+
let output = String(data: data, encoding: .utf8)!
62+
XCTAssertEqual(output, expected, file: file, line: line)
63+
} else {
64+
XCTFail("RE: \\(error)", file: file, line: line)
65+
}
66+
case .timedOut:
67+
XCTFail("TLE: Exceeded timeout of \\(timeLimit) seconds", file: file, line: line)
68+
default:
69+
XCTFail("Unrecognized error.", file: file, line: line)
70+
}
71+
}
72+
73+
private var productsDirectory: URL {
74+
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
75+
return bundle.bundleURL.deletingLastPathComponent()
76+
}
77+
fatalError("couldn't find the products directory")
78+
}
79+
}
80+
"""
81+
}
82+
}

0 commit comments

Comments
 (0)