///|
fn help_message() -> String {
let message =
#|Usage: tac [OPTION]... [FILE]...
#| --help display this help and exit
#| --version display version and exit
#|
#|Write each FILE to standard output, last line first.
#|With no FILE, or when FILE is -, read standard input.
#|
#| -b, --before Attach the separator before records.
#| -s, --separator=STRING Use STRING instead of newline.
#| -r, --regex Use an Emacs-style byte regular expression.
#|
#|Nonseekable input and regex mode require temporary-directory write access.
#|Regex mode retains the complete input for arbitrary-span byte matching.
message
}
///|
async fn main {
let metadata_args = @env.args()[1:]
if metadata_args.length() == 1 && metadata_args[0] == "--version" {
@stdio.stdout.write("tac 0.1.4\n")
return
}
let parsed = @cli.parse(@env.args()[1:], [
@cli.flag("before", short='b'),
@cli.option("separator", short='s'),
@cli.flag("regex", short='r'),
@cli.flag("help"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("tac: \{message}: '\{option}'\n")
@sys.exit(2)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write(help_message() + "\n")
return
}
let value = parsed.last_value("separator").unwrap_or("\n")
if parsed.contains("regex") && value.is_empty() {
@stdio.stderr.write("tac: separator cannot be empty\n")
@sys.exit(1)
return
}
let regex = try {
if parsed.contains("regex") {
Some(@bytere.compile(@bytere.argument(value), emacs=true))
} else {
None
}
} catch {
@bytere.PatternError(message) => {
let message = match message {
"unmatched opening parenthesis" => "Unmatched ( or \\("
"unmatched closing parenthesis" => "Unmatched ) or \\)"
"unterminated bracket expression" => "Unmatched [, [^, [:, [., or [="
"trailing backslash in regular expression" => "Trailing backslash"
"invalid backreference" => "Invalid back reference"
_ => message
}
@stdio.stderr.write("tac: " + message + "\n")
@sys.exit(1)
return
}
error => {
@stdio.stderr.write("tac: " + error.to_string() + "\n")
@sys.exit(1)
return
}
}
let separator = if value.is_empty() { b"\x00" } else { @utf8.encode(value) }
let paths = if parsed.operands.is_empty() { ["-"] } else { parsed.operands }
let mut status = 0
for path in paths {
let mut opened = path == "-"
try {
if path == "-" {
reverse_spooled(
@stdio.stdin,
separator,
parsed.contains("before"),
regex~,
)
} else {
let file = @fs.open(path, mode=ReadOnly)
opened = true
defer file.close()
if file.kind() == @fs.FileKind::Directory {
@stdio.stderr.write("tac: " + path + ": read error: Is a directory\n")
status = 1
continue
}
if file.kind() == @fs.FileKind::Regular && regex is None {
if has_extent(file, file.size()) {
reverse_file(file, separator, parsed.contains("before"))
} else {
reverse_spooled(file, separator, parsed.contains("before"))
}
} else {
reverse_spooled(file, separator, parsed.contains("before"), regex~)
}
}
} catch {
error => {
let message = error_message(error)
if path == "-" {
@stdio.stderr.write(
"tac: 'standard input': read error: " +
message +
"\ntac: -: " +
message +
"\n",
)
} else if opened {
@stdio.stderr.write(
"tac: " + path + ": read error: " + message + "\n",
)
} else {
@stdio.stderr.write(
"tac: failed to open '" +
path.replace_all(old="'", new="'\\''") +
"' for reading: " +
message +
"\n",
)
}
status = 1
}
}
}
if status != 0 {
@sys.exit(status)
}
}
///|
fn error_message(error : Error) -> String {
match error {
@os_error.OSError(code, ..) as error =>
if error.is_ENOENT() {
"No such file or directory"
} else if error.is_EACCES() {
"Permission denied"
} else if error.is_ENOTDIR() {
"Not a directory"
} else if !@platform.is_windows() && code == 21 {
"Is a directory"
} else if !@platform.is_windows() && code == 9 {
"Bad file descriptor"
} else {
error.to_string()
}
_ => error.to_string()
}
}