Skip to content

Commit

Permalink
internal: move SectionCapture to section_capture module
Browse files Browse the repository at this point in the history
  • Loading branch information
lijunchen committed Jul 9, 2024
1 parent 6e0dab1 commit 84b8414
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 99 deletions.
1 change: 1 addition & 0 deletions crates/moonbuild/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod fmt;
pub mod gen;
pub mod new;
pub mod runtest;
pub mod section_capture;
pub mod upgrade;

use sysinfo::{ProcessExt, System, SystemExt};
Expand Down
101 changes: 2 additions & 99 deletions crates/moonbuild/src/runtest.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::section_capture::{handle_stdout, SectionCapture};

use super::gen;
use anyhow::{bail, Context};
use moonutil::common::{
Expand All @@ -7,7 +9,6 @@ use moonutil::common::{
use moonutil::module::ModuleDB;
use n2::load::State;
use serde::{Deserialize, Serialize};
use std::io::BufRead;
use std::process::Command;
use std::{path::Path, process::Stdio};

Expand Down Expand Up @@ -39,104 +40,6 @@ pub fn run_js(path: &Path, target_dir: &Path) -> anyhow::Result<TestStatistics>
run("node", path, target_dir)
}

struct SectionCapture<'a> {
begin_delimiter: &'a str,
end_delimiter: &'a str,
capture_buffer: String,
include_delimiters: bool,
found_begin: bool,
found_end: bool,
}

enum LineCaptured {
All,
Prefix(usize), // noncaptured start index
Suffix(usize), // noncaptured end index
}

impl<'a> SectionCapture<'a> {
pub fn new(begin_delimiter: &'a str, end_delimiter: &'a str, include_delimiters: bool) -> Self {
SectionCapture {
begin_delimiter,
end_delimiter,
capture_buffer: String::new(),
include_delimiters,
found_begin: false,
found_end: false,
}
}

/// Feed a line into the capture buffer. The line should contain the newline character.
pub fn feed_line(&mut self, line: &str) -> Option<LineCaptured> {
if line.trim_end().ends_with(self.begin_delimiter) {
self.found_begin = true;
self.found_end = false;
if self.include_delimiters {
self.capture_buffer.push_str(line);
}
let end_index = line.trim_end().len() - self.begin_delimiter.len();
return Some(LineCaptured::Suffix(end_index));
}
if self.found_begin && line.starts_with(self.end_delimiter) {
self.found_end = true;
if self.include_delimiters {
self.capture_buffer.push_str(line);
}
let start_index = self.end_delimiter.len();
if line.trim().len() == self.end_delimiter.len() {
// The whole line is just the end delimiter.
return Some(LineCaptured::All);
} else {
return Some(LineCaptured::Prefix(start_index));
}
}
if self.found_begin && !self.found_end {
self.capture_buffer.push_str(line);
return Some(LineCaptured::All);
}
None
}

/// Returns the captured section if the section is complete.
pub fn finish(self) -> Option<String> {
if self.found_begin && self.found_end {
Some(self.capture_buffer)
} else {
None
}
}
}

/// Pipes the child stdout to stdout, with the ability to capture sections of the output.
/// If any of the captures captures a line, the line will not be printed to stdout.
/// The captures are processed in order: a line is captured by the first capture that captures it.
/// The program should not print overlapping sections, as the captures are not aware of each other.
fn handle_stdout<P: FnMut(&str)>(
stdout: &mut impl BufRead,
captures: &mut [&mut SectionCapture],
mut print: P,
) -> anyhow::Result<()> {
let mut buf = String::new();

loop {
buf.clear();
let n = stdout.read_line(&mut buf)?;
if n == 0 {
break;
}
let capture_status = captures
.iter_mut()
.find_map(|capture| capture.feed_line(&buf));
match capture_status {
None => print(&buf),
Some(LineCaptured::All) => {}
Some(LineCaptured::Prefix(start_index)) => print(&buf[start_index..]),
Some(LineCaptured::Suffix(end_index)) => print(&buf[..end_index]),
}
}
Ok(())
}

fn run(command: &str, path: &Path, target_dir: &Path) -> anyhow::Result<TestStatistics> {
let mut execution = Command::new(command)
.arg(path)
Expand Down
164 changes: 164 additions & 0 deletions crates/moonbuild/src/section_capture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use std::io::BufRead;

pub struct SectionCapture<'a> {
begin_delimiter: &'a str,
end_delimiter: &'a str,
capture_buffer: String,
include_delimiters: bool,
found_begin: bool,
found_end: bool,
}

pub enum LineCaptured {
All,
Prefix(usize), // noncaptured start index
Suffix(usize), // noncaptured end index
}

impl<'a> SectionCapture<'a> {
pub fn new(begin_delimiter: &'a str, end_delimiter: &'a str, include_delimiters: bool) -> Self {
SectionCapture {
begin_delimiter,
end_delimiter,
capture_buffer: String::new(),
include_delimiters,
found_begin: false,
found_end: false,
}
}

/// Feed a line into the capture buffer. The line should contain the newline character.
pub fn feed_line(&mut self, line: &str) -> Option<LineCaptured> {
if line.trim_end().ends_with(self.begin_delimiter) {
self.found_begin = true;
self.found_end = false;
if self.include_delimiters {
self.capture_buffer.push_str(line);
}
let end_index = line.trim_end().len() - self.begin_delimiter.len();
return Some(LineCaptured::Suffix(end_index));
}
if self.found_begin && line.starts_with(self.end_delimiter) {
self.found_end = true;
if self.include_delimiters {
self.capture_buffer.push_str(line);
}
let start_index = self.end_delimiter.len();
if line.trim().len() == self.end_delimiter.len() {
// The whole line is just the end delimiter.
return Some(LineCaptured::All);
} else {
return Some(LineCaptured::Prefix(start_index));
}
}
if self.found_begin && !self.found_end {
self.capture_buffer.push_str(line);
return Some(LineCaptured::All);
}
None
}

/// Returns the captured section if the section is complete.
pub fn finish(self) -> Option<String> {
if self.found_begin && self.found_end {
Some(self.capture_buffer)
} else {
None
}
}
}

/// Pipes the child stdout to stdout, with the ability to capture sections of the output.
/// If any of the captures captures a line, the line will not be printed to stdout.
/// The captures are processed in order: a line is captured by the first capture that captures it.
/// The program should not print overlapping sections, as the captures are not aware of each other.
pub fn handle_stdout<P: FnMut(&str)>(
stdout: &mut impl BufRead,
captures: &mut [&mut SectionCapture],
mut print: P,
) -> anyhow::Result<()> {
let mut buf = String::new();

loop {
buf.clear();
let n = stdout.read_line(&mut buf)?;
if n == 0 {
break;
}
let capture_status = captures
.iter_mut()
.find_map(|capture| capture.feed_line(&buf));
match capture_status {
None => print(&buf),
Some(LineCaptured::All) => {}
Some(LineCaptured::Prefix(start_index)) => print(&buf[start_index..]),
Some(LineCaptured::Suffix(end_index)) => print(&buf[..end_index]),
}
}
Ok(())
}

#[test]
fn test_handle_output() {
let out = "abcde
---begin---
text
---end---
fghij";
let expected_out = "abcde
fghij";
let mut capture = SectionCapture::new("---begin---", "---end---", false);
let mut buf = String::new();
let mut captures = [&mut capture];
handle_stdout(
&mut std::io::BufReader::new(out.as_bytes()),
&mut captures,
|line| buf.push_str(line),
)
.unwrap();
assert_eq!(buf, expected_out);
assert_eq!(capture.finish().unwrap(), "text\n");
}

#[test]
fn test_handle_intermixed_output() {
let out = "abcde
blahblah---begin---
text
---end---blahblah
fghij";
let expected_out = "abcde
blahblahblahblah
fghij";
let mut capture = SectionCapture::new("---begin---", "---end---", false);
let mut buf = String::new();
let mut captures = [&mut capture];
handle_stdout(
&mut std::io::BufReader::new(out.as_bytes()),
&mut captures,
|line| buf.push_str(line),
)
.unwrap();
assert_eq!(buf, expected_out);
assert_eq!(capture.finish().unwrap(), "text\n");
}
#[test]
fn test_handle_wrong_output() {
let out = "abcde
blahblah---begin--
text
---end---blahblah
fghij";

let mut capture = SectionCapture::new("---begin---", "---end---", false);
let mut buf = String::new();
let mut captures = [&mut capture];
handle_stdout(
&mut std::io::BufReader::new(out.as_bytes()),
&mut captures,
|line| buf.push_str(line),
)
.unwrap();
assert_eq!(buf, out);
assert!(capture.finish().is_none());
}

0 comments on commit 84b8414

Please sign in to comment.