Skip to content
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

Fix 22 - accumulate remainder in Lines.makeAsyncIterator #23

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ let package = Package(
"Script"
]
),
.testTarget(
name: "ScriptTests",
dependencies: ["Script"]
),
.testTarget(
name: "ShwiftTests",
dependencies: ["Shwift"],
Expand Down
12 changes: 7 additions & 5 deletions Sources/Script/List Comprehensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void>
{
public func compactMap(
segmentingInputAt delimiter: Character = "\n",
withOutputTerminator terminator: String = "\n",
transform: @Sendable @escaping (String) async throws -> String?
) -> Shell.PipableCommand<Void> {
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)
}
}
}
Expand Down
17 changes: 14 additions & 3 deletions Sources/Shwift/Builtins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ 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..<lineBreak]
substring = substring[substring.index(after: lineBreak)...]
continuation.yield(remainder + String(line))
remainder = ""
}
remainder = String(substring)
remainder += String(substring)
}
if !remainder.isEmpty {
continuation.yield(String(remainder))
Expand All @@ -126,9 +126,20 @@ extension Builtin {
}

fileprivate let byteBuffers: ByteBuffers
fileprivate let delimiter: Character
}

/// Make a Lines iterator splitting at newlines
public var lines: Lines {
Lines(byteBuffers: byteBuffers)
segmented()
}

/// Make a Lines iterator yielding text segments between delimiters (like split).
///
/// - Parameter delimiter: Character separating input text to yield (and not itself yielded) Defaults to newline.
/// - Returns: Lines segmented by delimiter
public func segmented(by delimiter: Character = "\n") -> Lines {
Lines(byteBuffers: byteBuffers, delimiter: delimiter)
}

typealias ByteBuffers = AsyncCompactMapSequence<
Expand Down
76 changes: 76 additions & 0 deletions Tests/ScriptTests/Script Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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>}")
}
}

/// 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,
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
}
}
}