///|
fn digit(value : Int) -> Char {
if value < 10 {
('0'.to_int() + value).to_char().unwrap()
} else {
('a'.to_int() + value - 10).to_char().unwrap()
}
}
///|
fn radix_string(value : Int, radix : Int) -> String {
if value == 0 {
return "0"
}
let mut number = value
let mut result = ""
while number > 0 {
result = digit(number % radix).to_string() + result
number /= radix
}
result
}
///|
fn printable(byte : Byte, eight_bit : Bool) -> Bool {
byte == 0x09 || (byte >= 0x20 && byte <= 0x7E) || (eight_bit && byte >= 0x80)
}
///|
fn records(
data : Bytes,
minimum : Int,
eight_bit? : Bool = false,
) -> Array[(Int, Bytes)] {
let result : Array[(Int, Bytes)] = []
let mut start = 0
let mut index = 0
while index <= data.length() {
if index == data.length() || !printable(data[index], eight_bit) {
if index - start >= minimum {
result.push((start, data[start:index].to_owned()))
}
index += 1
start = index
} else {
index += 1
}
}
result
}
///|
fn help_message() -> String {
let message =
#|Usage: strings [OPTION]... [FILE]...
#|Print the sequences of printable characters in each FILE.
#| -a, --all scan the whole file
#| -f, --print-file-name print the file name before each string
#| -n, --bytes=N print sequences at least N bytes long
#| -o display offsets in octal
#| -t, --radix=RADIX display offsets in d, o, or x radix
#| -e, --encoding=ENC s: 7-bit bytes (default); S: 8-bit bytes
#| wide encodings b/l/B/L are unsupported
#| --help display this help and exit
#| --version display version and exit
message
}
///|
fn parse_radix(value : String) -> Int? {
match value {
"d" => Some(10)
"o" => Some(8)
"x" => Some(16)
_ => None
}
}
///|
async fn emit(
path : String,
data : Bytes,
minimum : Int,
prefix : Bool,
radix : Int?,
eight_bit : Bool,
) -> Unit {
for (offset, text) in records(data, minimum, eight_bit~) {
let mut line = ""
if prefix {
line += (if path == "-" { "{standard input}" } else { path }) + ": "
}
match radix {
Some(base) => {
let address = radix_string(offset, base)
line += " ".repeat((7 - address.length()).max(0)) + address + " "
}
None => ()
}
@stdio.stdout.write(line)
@stdio.stdout.write(text)
@stdio.stdout.write("\n")
}
}
///|
async fn main {
let parsed = @cli.parse(@env.args()[1:], [
@cli.flag("all", short='a'),
@cli.flag("print-file-name", short='f'),
@cli.option("bytes", short='n'),
@cli.flag("octal", short='o'),
@cli.option("radix", short='t'),
@cli.option("encoding", short='e'),
@cli.flag("help"),
@cli.flag("version"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("strings: \{message}: '\{option}'\n")
@sys.exit(1)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write(help_message())
return
}
if parsed.contains("version") {
@stdio.stdout.write("strings (cli) \{command_version}\n")
return
}
let minimum = @string.parse_int(parsed.last_value("bytes").unwrap_or("4")) catch {
_ => 0
}
if minimum < 1 {
@stdio.stderr.write("strings: invalid minimum string length\n")
@sys.exit(1)
return
}
if parsed.values("encoding").any(value => value != "s" && value != "S") {
@stdio.stderr.write(
"strings: unsupported encoding; use s (7-bit) or S (8-bit)\n",
)
@sys.exit(2)
return
}
if parsed.values("radix").any(value => parse_radix(value) is None) {
@stdio.stderr.write("strings: invalid radix\n")
@sys.exit(1)
return
}
let radix = if parsed.last_occurrence(["octal", "radix"]) == Some("octal") {
Some(8)
} else if parsed.contains("radix") {
match parse_radix(parsed.last_value("radix").unwrap_or("")) {
Some(value) => Some(value)
None => {
@stdio.stderr.write("strings: invalid radix\n")
@sys.exit(2)
return
}
}
} else {
None
}
// GNU's bare '-' selects whole-file scanning, rather than adding stdin
// between file operands. With no remaining files, read stdin once.
let files = parsed.operands.filter(path => path != "-")
let sources = if files.is_empty() { ["-"] } else { files }
let prefix = parsed.contains("print-file-name")
let mut failed = false
for path in sources {
let data = read_source(path) catch {
err => {
@stdio.stderr.write("strings: \{path}: \{err}\n")
failed = true
continue
}
}
emit(
path,
data,
minimum,
prefix,
radix,
parsed.last_value("encoding") == Some("S"),
)
}
if failed {
@sys.exit(1)
}
}
///|
async fn read_source(path : String) -> Bytes {
if path == "-" {
@stdio.stdin.read_all().binary()
} else {
@fs.read_file(path).binary()
}
}