///|
fn parse_delimiters(spec : String) -> Array[String] {
let delims : Array[String] = []
let chars : Array[Char] = spec.iter().collect()
let mut i = 0
while i < chars.length() {
if chars[i] == '\\' && i + 1 < chars.length() {
let out = match chars[i + 1] {
'n' => "\n"
't' => "\t"
'0' => ""
'\\' => "\\"
c => c.to_string()
}
delims.push(out)
i += 2
} else {
delims.push(chars[i].to_string())
i += 1
}
}
if delims.is_empty() {
delims.push("\t")
}
delims
}
///|
async fn paste_serial(
scanner : @stream.LineScanner,
delimiters : Array[String],
) -> Unit {
let mut index = 0
while scanner.next() is Some(line) {
if index > 0 {
@stdio.stdout.write(delimiters[(index - 1) % delimiters.length()])
}
@stdio.stdout.write(line.data)
index += 1
}
@stdio.stdout.write("\n")
}
///|
async fn paste_parallel(
scanners : Array[@stream.LineScanner],
delimiters : Array[String],
) -> Unit {
for ;; {
let row : Array[Bytes?] = []
let mut any = false
for scanner in scanners {
match scanner.next() {
Some(line) => {
row.push(Some(line.data))
any = true
}
None => row.push(None)
}
}
if !any {
break
}
for index, value in row {
if index > 0 {
@stdio.stdout.write(delimiters[(index - 1) % delimiters.length()])
}
if value is Some(data) {
@stdio.stdout.write(data)
}
}
@stdio.stdout.write("\n")
}
}
///|
async fn main {
let args = @env.args()[1:]
let parsed = @cli.parse(args, [
@cli.flag("serial", short='s'),
@cli.option("delimiters", short='d'),
@cli.flag("help"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("paste: \{message}: '\{option}'\n")
@sys.exit(2)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write("Usage: paste [-s] [-d LIST] [FILE...]\n")
return
}
let serial = parsed.contains("serial")
let delims = match parsed.last_value("delimiters") {
Some(spec) => parse_delimiters(spec)
None => ["\t"]
}
let files = parsed.operands
let sources = if files.is_empty() { ["-"] } else { files }
let mut stdin_count = 0
for path in sources {
if path == "-" {
stdin_count += 1
}
}
if stdin_count > 1 {
@stdio.stderr.write("paste: standard input may be specified only once\n")
@sys.exit(2)
return
}
let open_files : Array[@fs.File] = []
defer (for file in open_files {
file.close()
})
try {
if serial {
for path in sources {
if path == "-" {
paste_serial(@stream.LineScanner::new(@stdio.stdin), delims)
} else {
let file = @fs.open(path, mode=ReadOnly)
open_files.push(file)
paste_serial(@stream.LineScanner::new(file), delims)
}
}
} else {
let scanners : Array[@stream.LineScanner] = []
for path in sources {
if path == "-" {
scanners.push(@stream.LineScanner::new(@stdio.stdin))
} else {
let file = @fs.open(path, mode=ReadOnly)
open_files.push(file)
scanners.push(@stream.LineScanner::new(file))
}
}
paste_parallel(scanners, delims)
}
} catch {
err => {
@stdio.stderr.write("paste: \{err}\n")
@sys.exit(1)
return
}
}
}