///|
priv struct GrepInput {
reader : &@io.Reader
delimiter : Bytes
binary_mode : BinaryMode
mut bytes : Bytes
mut position : Int32
mut offset : Int64
mut number : Int64
mut binary : Bool
mut error : Error?
}
///|
fn GrepInput::new(
reader : &@io.Reader,
delimiter : Byte,
binary_mode : BinaryMode,
) -> GrepInput {
{
reader,
delimiter: Bytes::from_array([delimiter]),
binary_mode,
bytes: b"",
position: 0,
offset: 0L,
number: 0L,
binary: false,
error: None,
}
}
///|
// A record inside one immutable chunk is a view. Only a cross-chunk record
// needs accumulation; consumed prefixes are never rescanned or recopied.
async fn GrepInput::next(self : GrepInput) -> Record? {
if self.error is Some(error) {
raise error
}
let offset = self.offset
let mut pending : @buffer.Buffer? = None
for ;; {
if self.position == self.bytes.length() {
let chunk = self.reader.read_some(max_len=65536) catch {
error => {
self.error = Some(error)
None
}
}
match chunk {
None => {
if pending is Some(buffer) {
self.number += 1L
return Some({
data: buffer.to_bytes()[:],
terminated: false,
number: self.number,
offset,
})
}
if self.error is Some(error) {
raise error
}
return None
}
Some(bytes) => {
self.bytes = bytes
self.position = 0
if self.binary_mode != Text && bytes.find(b"\x00") is Some(_) {
self.binary = true
if self.binary_mode == WithoutMatch {
return None
}
}
}
}
}
let start = self.position
self.position = match self.bytes[start:].find(self.delimiter[:]) {
Some(relative) => start + relative
None => self.bytes.length()
}
let part = self.bytes[start:self.position]
self.offset += part.length().to_int64()
if self.position < self.bytes.length() {
self.position += 1
self.offset += 1L
self.number += 1L
let data = match pending {
Some(buffer) => {
buffer.write_bytes(part)
buffer.write_byte(self.delimiter[0])
let bytes = buffer.to_bytes()
bytes[:bytes.length() - 1]
}
None => part
}
return Some({ data, terminated: true, number: self.number, offset, })
}
if !part.is_empty() {
let buffer = match pending {
Some(buffer) => buffer
None => {
let buffer = @buffer.Buffer()
pending = Some(buffer)
buffer
}
}
buffer.write_bytes(part)
}
}
}
///|
fn Matcher::matches_record(
self : Matcher,
record : BytesView,
ignore_case : Bool,
) -> Bool {
match self {
Fixed(patterns, whole, word) => {
let line = if ignore_case { folded_bytes(record)[:] } else { record }
fixed_span(patterns, line, 0, whole, word) is Some(_)
}
Regular(_, _, _) => self.next_match(bytes_as_string(record), 0) is Some(_)
}
}
///|
async fn write_matches(
record : Record,
path : String,
matcher : Matcher,
options : GrepOptions,
prefix_filename : Bool,
output : &@io.Writer,
) -> Bool {
let next : (Int32) -> (Int32, Int32)? = match matcher {
Fixed(patterns, whole, word) => {
let line = if options.ignore_case {
folded_bytes(record.data)[:]
} else {
record.data
}
position => fixed_span(patterns, line, position, whole, word)
}
Regular(_, _, _) => {
let line = bytes_as_string(record.data)
position => matcher.next_match(line, position)
}
}
let mut position = 0
let first = next(position)
let mut hit = first
while hit is Some((start, end)) {
if end > start {
write_record(
{
data: record.data[start:end],
terminated: record.terminated && end == record.data.length(),
number: record.number,
offset: record.offset + start.to_int64(),
},
path,
options,
prefix_filename,
true,
output,
)
position = end
} else {
position = start + 1
}
if position > record.data.length() {
break
}
hit = next(position)
}
first is Some(_)
}
///|
async fn process_reader(
reader : &@io.Reader,
path : String,
matcher : Matcher,
options : GrepOptions,
prefix_filename : Bool,
stdout~ : &@io.Writer = @stdio.stdout,
stderr~ : &@io.Writer = @stdio.stderr,
) -> Bool {
let input = GrepInput::new(
reader,
if options.zero_terminated {
b'\x00'
} else {
b'\n'
},
if options.zero_terminated {
Text
} else {
options.binary_mode
},
)
let output = @io.BufferedWriter::new(stdout, size=65536)
let before : @deque.Deque[Record] = @deque.Deque([])
let mut selected_count = 0UL
let mut emitted_through = 0L
let mut after = 0
let mut binary_reported = false
let emit_records = !options.count &&
!options.files_with_matches &&
!options.files_without_match &&
!options.quiet
for ;; {
let record = {
errdefer output.flush()
input.next()
}
guard record is Some(record) else { break }
if emit_records &&
options.only_matching &&
!options.invert &&
(!input.binary || options.binary_mode != Report) {
if write_matches(record, path, matcher, options, prefix_filename, output) {
selected_count += 1UL
}
continue
}
let matched = matcher.matches_record(record.data, options.ignore_case)
let keep = if options.invert { !matched } else { matched }
if keep {
selected_count += 1UL
if options.quiet {
return true
}
if !options.count && options.files_with_matches {
output.write(path + "\n")
output.flush()
return true
}
}
if !emit_records {
continue
}
if input.binary && options.binary_mode == Report {
if keep && !binary_reported {
output.flush()
let name = if path == "-" { "(standard input)" } else { path }
stderr.write("grep: \{name}: binary file matches\n")
binary_reported = true
}
continue
}
if options.only_matching {
continue
}
if keep {
let first = match before.front() {
Some(record) => record.number
None => record.number
}
if options.context_enabled &&
emitted_through > 0L &&
first > emitted_through + 1L {
output.write("--\n")
}
while before.pop_front() is Some(context) {
if context.number > emitted_through {
write_record(context, path, options, prefix_filename, false, output)
emitted_through = context.number
}
}
write_record(record, path, options, prefix_filename, true, output)
emitted_through = record.number
after = options.after_context
} else if after > 0 {
write_record(record, path, options, prefix_filename, false, output)
emitted_through = record.number
after -= 1
}
if options.before_context > 0 {
before.push_back(record)
if before.length() > options.before_context {
ignore(before.pop_front())
}
}
}
if input.binary && options.binary_mode == WithoutMatch {
selected_count = 0UL
}
if options.count {
let prefix = if prefix_filename { path + ":" } else { "" }
output.write("\{prefix}\{selected_count}\n")
} else if options.files_without_match && selected_count == 0UL {
output.write(path + "\n")
}
output.flush()
if options.files_without_match {
selected_count == 0UL
} else {
selected_count > 0UL
}
}