///|
priv suberror InvalidCRecord {
InvalidCRecord
}
///|
priv struct RevOutput {
writer : &@io.Writer
mut failed : Bool
}
///|
impl @io.Writer for RevOutput with fn write_once(self, data, offset~, len~) {
errdefer {
self.failed = true
}
self.writer.write_once(data, offset~, len~)
}
///|
fn reverse_line(data : Bytes) -> Bytes {
FixedArray::makei(data.length(), index => data[data.length() - index - 1]).unsafe_reinterpret_as_bytes()
}
///|
fn help_message() -> String {
let message =
#|Usage: rev [OPTION]... [FILE]...
#| --help display this help and exit
#| --version display version and exit
#|
#|Reverse each line from ordered FILE operands, or standard input if absent.
#|The fixed Linux C-locale profile reverses bytes and rejects non-ASCII records.
#|An explicit - is a filename, as in the frozen util-linux profile.
#|Input streams one record at a time; memory follows the longest record.
message
}
///|
async fn main {
let metadata_args = @env.args()[1:]
if metadata_args.length() == 1 && metadata_args[0] == "--version" {
@stdio.stdout.write("rev 0.1.4\n")
return
}
let parsed = @cli.parse(@env.args()[1:], [@cli.flag("help")]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("rev: \{message}: '\{option}'\n")
@sys.exit(2)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write(help_message() + "\n")
return
}
if parsed.operands.is_empty() {
let lines = Ref(0L)
reverse_reader(@stdio.stdin, lines) catch {
error => {
@stdio.stderr.write(
"rev: stdin: " +
lines.val.to_string() +
": " +
error_message(error) +
"\n",
)
@sys.exit(1)
}
}
return
}
let mut status = 0
for path in parsed.operands {
let mut opened = false
let lines = Ref(0L)
try {
let file = @fs.open(path, mode=ReadOnly)
opened = true
defer file.close()
if file.kind() == @fs.FileKind::Directory {
@stdio.stderr.write("rev: " + path + ": 0: Is a directory\n")
status = 1
} else {
reverse_reader(file, lines)
}
} catch {
error => {
let prefix = if opened {
"rev: " + path + ": " + lines.val.to_string() + ": "
} else {
"rev: cannot open " + path + ": "
}
@stdio.stderr.write(prefix + error_message(error) + "\n")
status = 1
}
}
}
if status != 0 {
@sys.exit(status)
}
}
///|
async fn reverse_reader(
reader : &@io.Reader,
count : Ref[Int64],
writer~ : &@io.Writer = @stdio.stdout,
) -> Unit {
let lines = @stream.LineScanner::new(reader)
let sink : RevOutput = { writer, failed: false, }
let output = @io.BufferedWriter::new(sink, size=65536)
let processing_error = try {
while lines.next() is Some(line) {
if line.data.iter().any(byte => byte.to_int() >= 128) {
raise InvalidCRecord
}
output.write(reverse_line(line.data))
if line.terminated {
output.write(b"\n")
}
count.val += 1
}
None
} catch {
error => Some(error)
}
if !sink.failed {
output.flush()
}
if processing_error is Some(error) {
raise error
}
}
///|
fn error_message(error : Error) -> String {
match error {
InvalidCRecord => "Invalid or incomplete multibyte or wide character"
@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()
}
}