From a91cf2d32c1b562676a8442c499322a6e160084e Mon Sep 17 00:00:00 2001 From: wti Date: Mon, 18 Mar 2024 12:49:55 -0700 Subject: [PATCH 1/5] Script tests: outputOf on echo & echo pipe to map --- Package.swift | 4 ++ Tests/ScriptTests/Script Tests.swift | 64 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 Tests/ScriptTests/Script Tests.swift diff --git a/Package.swift b/Package.swift index 31ab889..b48ee45 100644 --- a/Package.swift +++ b/Package.swift @@ -41,6 +41,10 @@ let package = Package( "Script" ] ), + .testTarget( + name: "ScriptTests", + dependencies: ["Script"] + ), .testTarget( name: "ShwiftTests", dependencies: ["Shwift"], diff --git a/Tests/ScriptTests/Script Tests.swift b/Tests/ScriptTests/Script Tests.swift new file mode 100644 index 0000000..6c9bd0c --- /dev/null +++ b/Tests/ScriptTests/Script Tests.swift @@ -0,0 +1,64 @@ +import XCTest +import Script + +final class ScriptCoreTests: XCTestCase { + + func testEcho() async throws { + try runInScript { + var result: String + result = try await outputOf { + try await echo("1", "2") + } + // fyi, trailing newline stripped by outputOf + XCTAssertEqual("1 2", result, "echo 1 2") + + result = try await outputOf { + try await echo("1", "2", separator: ",", terminator: ";") + } + XCTAssertEqual("1,2;", result, "echo 1,2;") + } + } + + func testEchoMap() async throws { + try runInScript { + var result: String + result = try await outputOf { + // inject newline so map op runs on head then tail + try await echo("1", "2", "\n", "3", "4") | map(){"<\($0)>"} + } + XCTAssertEqual("<1 2 >\n< 3 4>", result, "echo 1 2 \n 3 4 | map{<$0>}") + } + } + + /// Run test in Script.run() context + private func runInScript( + _ op: @escaping RunInScriptProxy.Op, + caller: StaticString = #function, + callerLine: UInt = #line + ) throws { + let e = expectation(description: "\(caller):\(callerLine)") + try RunInScriptProxy(op, e).run() // sync call preferred + wait(for: [e], timeout: 2) + } + + private struct RunInScriptProxy: Script { + typealias Op = () async throws -> Void + var op: Op? + var e: XCTestExpectation? + init(_ op: @escaping Op, _ e: XCTestExpectation) { + self.op = op + self.e = e + } + func run() async throws { + defer { e?.fulfill() } + try await op?() + } + // Codable + init() {} + var i = 0 + private enum CodingKeys: String, CodingKey { + case i + } + } +} + From 67472c7e365c200d8ca033abf11023290a9001f8 Mon Sep 17 00:00:00 2001 From: wti Date: Mon, 18 Mar 2024 12:51:45 -0700 Subject: [PATCH 2/5] Lines.segmented(by delimiter:..) --- Sources/Shwift/Builtins.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/Shwift/Builtins.swift b/Sources/Shwift/Builtins.swift index 748f759..a3d9762 100644 --- a/Sources/Shwift/Builtins.swift +++ b/Sources/Shwift/Builtins.swift @@ -105,7 +105,7 @@ extension Builtin { at: buffer.readerIndex, length: buffer.readableBytes)! var substring = readString[readString.startIndex...] - while let lineBreak = substring.firstIndex(of: "\n") { + while let lineBreak = substring.firstIndex(of: delimiter) { let line = substring[substring.startIndex.. Lines { + Lines(byteBuffers: byteBuffers, delimiter: delimiter) } typealias ByteBuffers = AsyncCompactMapSequence< From 77cf6fdfe6fba0c91d704cbffbb7559fc742c64b Mon Sep 17 00:00:00 2001 From: wti Date: Mon, 18 Mar 2024 12:54:34 -0700 Subject: [PATCH 3/5] compactMap(segmentingInputAt:, withOutputTerminator:) --- Sources/Script/List Comprehensions.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/Script/List Comprehensions.swift b/Sources/Script/List Comprehensions.swift index 88a295f..d3e1fe0 100644 --- a/Sources/Script/List Comprehensions.swift +++ b/Sources/Script/List Comprehensions.swift @@ -10,15 +10,17 @@ public func map(transform: @Sendable @escaping (String) async throws -> String) compactMap(transform: transform) } -public func compactMap(transform: @Sendable @escaping (String) async throws -> String?) - -> Shell.PipableCommand -{ +public func compactMap( + segmentingInputAt delimiter: Character = "\n", + withOutputTerminator terminator: String = "\n", + transform: @Sendable @escaping (String) async throws -> String? +) -> Shell.PipableCommand { Shell.PipableCommand { try await Shell.invoke { shell, invocation in try await invocation.builtin { channel in - for try await line in channel.input.lines.compactMap(transform) { + for try await line in channel.input.segmented(by: delimiter).compactMap(transform) { try await channel.output.withTextOutputStream { stream in - print(line, to: &stream) + print(line, terminator: terminator, to: &stream) } } } From 5333f620016086b0465c35aaa5763194861fb3e8 Mon Sep 17 00:00:00 2001 From: wti Date: Mon, 18 Mar 2024 13:02:01 -0700 Subject: [PATCH 4/5] Test #22: remainder dropped when no delimiter --- Tests/ScriptTests/Script Tests.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/ScriptTests/Script Tests.swift b/Tests/ScriptTests/Script Tests.swift index 6c9bd0c..c7eb660 100644 --- a/Tests/ScriptTests/Script Tests.swift +++ b/Tests/ScriptTests/Script Tests.swift @@ -30,6 +30,18 @@ final class ScriptCoreTests: XCTestCase { } } + /// Demo [#22 dropped remainder](https://github.com/GeorgeLyon/Shwift/issues/22) + func testEchoMapWithDelimiters() async throws { + try runInScript { + let result = try await outputOf { + try await echo("1", "2", separator: ",", terminator: "") + | compactMap(segmentingInputAt: ",", withOutputTerminator: "|") { "<\($0)>"} + } + let exp = "<1>|<2>|" + XCTAssertEqual(exp, result, "echo 1,2 -map-> <1>|<2>|") + } + } + /// Run test in Script.run() context private func runInScript( _ op: @escaping RunInScriptProxy.Op, From 42156b243f30ebe08e31ffb096c082122e1ec3f9 Mon Sep 17 00:00:00 2001 From: wti Date: Mon, 18 Mar 2024 13:03:56 -0700 Subject: [PATCH 5/5] Fix #22: accumulate remainder in Lines iterator --- Sources/Shwift/Builtins.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Shwift/Builtins.swift b/Sources/Shwift/Builtins.swift index a3d9762..21d9bde 100644 --- a/Sources/Shwift/Builtins.swift +++ b/Sources/Shwift/Builtins.swift @@ -111,7 +111,7 @@ extension Builtin { continuation.yield(remainder + String(line)) remainder = "" } - remainder = String(substring) + remainder += String(substring) } if !remainder.isEmpty { continuation.yield(String(remainder))