///|
fn nl_command() -> @argparse.Command {
Command(
"nl",
about="Number lines of each input.",
options=[
OptionArg(
"body-numbering",
short='b',
about="Numbering style: a (all lines), t (nonempty lines), n (none).",
default_values=["t"],
),
OptionArg(
"number-width",
short='w',
about="Width of the line number column.",
default_values=["6"],
),
OptionArg(
"number-separator",
short='s',
about="String between number and line.",
default_values=["\t"],
),
],
positionals=[
PositionArg(
"files",
about="[file...] ('-' or no file reads stdin)",
num_args=ValueRange(lower=0),
),
],
disable_help_subcommand=true,
)
}
///|
fn option_value(
matches : @argparse.Matches,
name : String,
fallback : String,
) -> String {
match matches.values.get(name) {
Some(vals) =>
if vals.is_empty() {
fallback
} else {
vals[vals.length() - 1]
}
None => fallback
}
}
///|
fn split_lines(text : String) -> Array[String] {
if text is "" {
return []
}
let lines : Array[String] = text.split("\n").map(v => v.to_owned()).collect()
if text.has_suffix("\n") {
ignore(lines.pop())
}
lines
}
///|
fn pad_left(text : String, width : Int) -> String {
let sb = StringBuilder()
for _ in 0..<(width - text.length()) {
sb.write_char(' ')
}
sb.write_string(text)
sb.to_string()
}
///|
fn spaces(width : Int) -> String {
let sb = StringBuilder()
for _ in 0.. String {
if path == "-" {
@stdio.stdin.read_all().text()
} else {
@fs.read_file_to_string(path)
}
}
///|
async fn main {
let args = @env.args()[1:]
let command = nl_command()
let matches = command.parse(argv=args, env=Map([])) catch {
err => {
@stdio.stderr.write("\{err}\n")
@sys.exit(2)
return
}
}
let style = option_value(matches, "body-numbering", "t")
if !(style is ("a" | "t" | "n")) {
@stdio.stderr.write("nl: invalid body numbering style: '\{style}'\n")
@sys.exit(2)
return
}
let width_text = option_value(matches, "number-width", "6")
let width = @string.parse_int(width_text) catch {
_ => {
@stdio.stderr.write(
"nl: invalid line number field width: '\{width_text}'\n",
)
@sys.exit(2)
return
}
}
if width < 1 {
@stdio.stderr.write(
"nl: invalid line number field width: '\{width_text}'\n",
)
@sys.exit(2)
return
}
let separator = option_value(matches, "number-separator", "\t")
let files = matches.values.get("files").unwrap_or([])
let sources = if files.is_empty() { ["-"] } else { files }
let mut number = 1
let mut failed = false
for path in sources {
let text = read_source_text(path) catch {
err => {
@stdio.stderr.write("nl: \{err}\n")
failed = true
continue
}
}
for line in split_lines(text) {
let numbered = match style {
"a" => true
"t" => line != ""
_ => false
}
if numbered {
@stdio.stdout.write(
pad_left(number.to_string(), width) + separator + line + "\n",
)
number += 1
} else {
@stdio.stdout.write(spaces(width) + line + "\n")
}
}
}
if failed {
@sys.exit(1)
}
}