///|
async fn run(args : ArrayView[String]) -> Int {
let parsed = @cli.parse(args, [
@cli.flag("quiet", short='n'),
@cli.flag("silent"),
@cli.flag("regexp-extended", short='E'),
@cli.flag("extended", short='r'),
@cli.option("expression", short='e'),
@cli.option("file", short='f'),
@cli.flag("help"),
@cli.flag("version"),
])
if parsed.contains("help") {
@stdio.stdout.write(
"Usage: sed [-nE] [-e SCRIPT] [-f SCRIPTFILE] [SCRIPT] [FILE...]\n --help display this help and exit\n --version display version and exit\nStream editor: p, d, q, =, s/RE/REPLACEMENT/[gNp].\nAddresses: line number, $, /RE/, ranges and !. BRE default; -E enables ERE.\nIn-place editing, hold space, branches, groups and regex backreferences are not supported.\n",
)
return 0
}
if parsed.contains("version") {
@stdio.stdout.write("sed 0.1.1\n")
return 0
}
let scripts : Array[String] = []
let expressions = parsed.values("expression")
let script_files = parsed.values("file")
let mut e = 0
let mut f = 0
for option in parsed.order {
if option == "expression" {
scripts.push(@bytere.argument(expressions[e]))
e += 1
}
if option == "file" {
scripts.push(@bytere.text(@fs.read_file(script_files[f]).binary()))
f += 1
}
}
let files = parsed.operands
if scripts.is_empty() {
guard !files.is_empty() else { fail("missing script") }
scripts.push(@bytere.argument(files.remove(0)))
}
let source = scripts.join("\n")
let commands = compile(
source,
parsed.contains("regexp-extended") || parsed.contains("extended"),
)
let quiet = parsed.contains("quiet") ||
parsed.contains("silent") ||
source.has_prefix("#n")
if files.is_empty() {
files.push("-")
}
let need_last = commands.any(c => {
c.first is Some(Last) || c.last is Some(Last)
})
let mut number = 0
let mut status = 0
let mut pending : @stream.Line? = None
let runtime : Runtime = { previous: None, missing_newline: false, }
for path in files {
let quit = try {
let handle = if path == "-" {
None
} else {
Some(@fs.open(path, mode=ReadOnly))
}
defer (if handle is Some(file) { file.close() })
let reader : &@io.Reader = match handle {
Some(file) => (file : &@io.Reader)
None => (@stdio.stdin : &@io.Reader)
}
let scanner = @stream.LineScanner::new(reader)
let mut quit = false
while scanner.next() is Some(line) {
if need_last {
if pending is Some(previous) {
number += 1
if execute_line(commands, previous, number, false, quiet, runtime) {
quit = true
break
}
}
pending = Some(line)
} else {
number += 1
if execute_line(commands, line, number, false, quiet, runtime) {
quit = true
break
}
}
}
quit
} catch {
SedError(_) as err => raise err
@bytere.PatternError(_) as err => raise err
err => {
@stdio.stderr.write("sed: \{path}: \{err}\n")
status = 2
false
}
}
if quit {
return status
}
}
if pending is Some(line) {
ignore(execute_line(commands, line, number + 1, true, quiet, runtime))
}
status
}
///|
async fn main {
let status = run(@env.args()[1:]) catch {
SedError(message) | @bytere.PatternError(message) => {
@stdio.stderr.write("sed: \{message}\n")
1
}
err => {
@stdio.stderr.write("sed: \{err}\n")
1
}
}
if status != 0 {
@sys.exit(status)
}
}