///|
fn split_whitespace(line : String) -> Array[String] {
let fields : Array[String] = []
let mut start = 0
let mut index = 0
let mut in_field = false
while index <= line.length() {
let at_end = index == line.length()
let blank = !at_end && (line[index] == ' ' || line[index] == '\t')
if at_end || blank {
if in_field {
fields.push(line[start:index].to_owned())
in_field = false
}
index += 1
} else {
if !in_field {
start = index
in_field = true
}
index += 1
}
}
fields
}
///|
fn split_fields(line : String, separator : String?) -> Array[String] {
match separator {
Some(value) => line.split(value).map(item => item.to_owned()).collect()
None => split_whitespace(line)
}
}
///|
fn table(
lines : Array[String],
separator : String?,
output_separator : String,
) -> String {
let rows : Array[Array[String]] = []
for line in lines {
if line.trim() != "" {
rows.push(split_fields(line, separator))
}
}
let widths : Array[Int] = []
for row in rows {
for index, field in row {
while widths.length() <= index {
widths.push(0)
}
widths[index] = widths[index].max(field.length())
}
}
let output = StringBuilder()
for row_index, row in rows {
for index, field in row {
output.write_string(field)
if index + 1 < row.length() {
let padding = widths[index] - field.length()
for _ in 0.. String {
let message =
#|Usage: column [OPTION]... [FILE]...
#|Columnate input, or copy input when table mode is not selected.
#| -t, --table create a table
#| -s, --separator=SEP use SEP as the input field separator
#| -o, --output-separator=SEP
#| use SEP between aligned columns
#| --help display this help and exit
#| --version display version and exit
message
}
///|
async fn read_source(path : String) -> String {
if path == "-" {
@stdio.stdin.read_all().text()
} else {
@fs.read_file(path).text()
}
}
///|
async fn main {
let parsed = @cli.parse(@env.args()[1:], [
@cli.flag("table", short='t'),
@cli.option("separator", short='s'),
@cli.option("output-separator", short='o'),
@cli.flag("help"),
@cli.flag("version"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("column: \{message}: '\{option}'\n")
@sys.exit(2)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write(help_message())
return
}
if parsed.contains("version") {
@stdio.stdout.write("column (cli) \{command_version}\n")
return
}
let separator = parsed.last_value("separator")
if separator is Some(value) && value.length() != 1 {
@stdio.stderr.write("column: separator must be one byte\n")
@sys.exit(2)
return
}
let output_separator = parsed.last_value("output-separator").unwrap_or(" ")
let sources = if parsed.operands.is_empty() { ["-"] } else { parsed.operands }
let mut failed = false
let mut all = ""
for path in sources {
let input = read_source(path) catch {
err => {
@stdio.stderr.write("column: \{path}: \{err}\n")
failed = true
continue
}
}
if parsed.contains("table") {
let value = table(
input.split("\n").map(line => line.to_owned()).collect(),
separator,
output_separator,
)
all += value
} else {
all += input
}
}
@stdio.stdout.write(all)
if failed {
@sys.exit(1)
}
}