Skip to content

MCP server to run/read/write tests with AI #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions cmp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Library functions for CLT comparison functionality

use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
use regex::Regex;

#[derive(Debug)]
pub enum MatchingPart {
Static(String),
Pattern(String),
}

pub struct PatternMatcher {
config: HashMap<String, String>,
var_regex: Regex,
}

impl PatternMatcher {
/// Initialize struct by using file name of the variables description for patterns
/// If the option is none, we just will have empty map of keys for patterns
/// And in that case we will use only raw regexes to validate
pub fn new(file_name: Option<String>) -> Result<Self, Box<dyn std::error::Error>> {
let config = match file_name {
Some(file_name) => Self::parse_config(file_name)?,
None => HashMap::new(),
};

let var_regex = Regex::new(r"%\{[A-Z]{1}[A-Z_0-9]*\}")?;
Ok(Self { config, var_regex })
}

/// Validate line from .rec file and line from .rep file
/// by using open regex patterns and matched variables
/// and return true or false in case if we have diff or not
pub fn has_diff(&self, rec_line: String, rep_line: String) -> bool {
let rec_line = self.replace_vars_to_patterns(rec_line);
let parts = self.split_into_parts(&rec_line);
let mut last_index = 0;

for part in parts {
match part {
MatchingPart::Static(static_part) => {
if rep_line[last_index..].starts_with(&static_part) {
last_index += static_part.len();
} else {
return true;
}
}
MatchingPart::Pattern(pattern) => {
let pattern_regex = Regex::new(&pattern).unwrap();
if let Some(mat) = pattern_regex.find(&rep_line[last_index..]) {
last_index += mat.end();
} else {
return true;
}
}
}
}

last_index != rep_line.len()
}

/// Helper method to split line into parts
/// To make it possible to validate pattern matched vars and static parts
pub fn split_into_parts(&self, rec_line: &str) -> Vec<MatchingPart> {
let mut parts = Vec::new();

let first_splits: Vec<&str> = rec_line.split("#!/").collect();
for first_split in first_splits {
let second_splits: Vec<&str> = first_split.split("/!#").collect();
if second_splits.len() == 1 {
parts.push(MatchingPart::Static(second_splits.first().unwrap().to_string()));
} else {
for (i, second_split) in second_splits.iter().enumerate() {
if i % 2 == 1 {
parts.push(MatchingPart::Static(second_split.to_string()));
} else {
parts.push(MatchingPart::Pattern(second_split.to_string()));
}
}
}
}
parts
}

/// Helper function that go through matched variable patterns in line
/// And replace it all with values from our parsed config
/// So we have raw regex to validate as an output
pub fn replace_vars_to_patterns(&self, line: String) -> String {
let result = self.var_regex.replace_all(&line, |caps: &regex::Captures| {
let matched = &caps[0];
let key = matched[2..matched.len() - 1].to_string();
self.config.get(&key).unwrap_or(&matched.to_string()).clone()
});

result.into_owned()
}

/// Helper to parse the variables into config map when we pass path to the file
fn parse_config(file_name: String) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let mut config: HashMap<String, String> = HashMap::new();

let file_path = std::path::Path::new(&file_name);
let file = File::open(&file_path)?;
let reader = BufReader::new(file);

for line in reader.lines() {
let line = line?.trim().to_string();
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 2 {
config.insert(
parts[0].trim().to_string(),
format!("#!/{}/!#", parts[1].trim())
);
}
}

Ok(config)
}
}
121 changes: 3 additions & 118 deletions cmp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, BufReader, BufRead, SeekFrom, Seek, self};
use std::env;
use std::path::Path;
use regex::Regex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use std::io::Write;
use tempfile;

// Import from lib
pub use cmp::{PatternMatcher, MatchingPart};

enum Diff {
Plus,
Minus
Expand Down Expand Up @@ -236,122 +237,6 @@ fn main() {
}
}

enum MatchingPart {
Static(String),
Pattern(String),
}

struct PatternMatcher {
config: HashMap<String, String>,
var_regex: Regex,
}

impl PatternMatcher {
/// Initialize struct by using file name of the variables description for patterns
/// If the option is none, we just will have empty map of keys for patterns
/// And in that case we will use only raw regexes to validate
fn new(file_name: Option<String>) -> Result<Self, Box<dyn std::error::Error>> {
let config = match file_name {
Some(file_name) => Self::parse_config(file_name)?,
None => HashMap::new(),
};

let var_regex = Regex::new(r"%\{[A-Z]{1}[A-Z_0-9]*\}")?;
Ok(Self { config, var_regex })
}

/// Validate line from .rec file and line from .rep file
/// by using open regex patterns and matched variables
/// and return true or false in case if we have diff or not
fn has_diff(&self, rec_line: String, rep_line: String) -> bool {
let rec_line = self.replace_vars_to_patterns(rec_line);
let parts = self.split_into_parts(&rec_line);
let mut last_index = 0;

for part in parts {
match part {
MatchingPart::Static(static_part) => {
if rep_line[last_index..].starts_with(&static_part) {
last_index += static_part.len();
} else {
return true;
}
}
MatchingPart::Pattern(pattern) => {
let pattern_regex = Regex::new(&pattern).unwrap();
if let Some(mat) = pattern_regex.find(&rep_line[last_index..]) {
last_index += mat.end();
} else {
return true;
}
}
}
}

last_index != rep_line.len()
}

/// Helper method to split line into parts
/// To make it possible to validate pattern matched vars and static parts
///
fn split_into_parts(&self, rec_line: &str) -> Vec<MatchingPart> {
let mut parts = Vec::new();

let first_splits: Vec<&str> = rec_line.split("#!/").collect();
for first_split in first_splits {
let second_splits: Vec<&str> = first_split.split("/!#").collect();
if second_splits.len() == 1 {
parts.push(MatchingPart::Static(second_splits.first().unwrap().to_string()));
} else {
for (i, second_split) in second_splits.iter().enumerate() {
if i % 2 == 1 {
parts.push(MatchingPart::Static(second_split.to_string()));
} else {
parts.push(MatchingPart::Pattern(second_split.to_string()));
}
}
}

}
parts
}

/// Helper function that go through matched variable patterns in line
/// And replace it all with values from our parsed config
/// So we have raw regex to validate as an output
fn replace_vars_to_patterns(&self, line: String) -> String {
let result = self.var_regex.replace_all(&line, |caps: &regex::Captures| {
let matched = &caps[0];
let key = matched[2..matched.len() - 1].to_string();
self.config.get(&key).unwrap_or(&matched.to_string()).clone()
});

result.into_owned()
}

/// Helper to parse the variables into config map when we pass path to the file
fn parse_config(file_name: String) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let mut config: HashMap<String, String> = HashMap::new();

let file_path = Path::new(&file_name);
let file = File::open(&file_path)?;
let reader = BufReader::new(file);

for line in reader.lines() {
let line = line?.trim().to_string();
let parts: Vec<&str> = line.split_whitespace().collect(); // adjust this based on how your file is structured
if parts.len() == 2 {
config.insert(
parts[0].trim().to_string(),
format!("#!/{}/!#", parts[1].trim())
);
}
}

Ok(config)
}
}

fn move_cursor_to_line<R: BufRead + Seek>(reader: &mut R, command_prefix: &str) -> io::Result<()> {
let mut line = String::new();

Expand Down
18 changes: 18 additions & 0 deletions mcp/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[build]
# Default to static linking
rustflags = ["-C", "target-feature=+crt-static"]

[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
rustflags = ["-C", "target-feature=+crt-static"]

[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=+crt-static"]

# For macOS static linking (as much as possible)
[target.x86_64-apple-darwin]
rustflags = ["-C", "target-feature=+crt-static"]

[target.aarch64-apple-darwin]
rustflags = ["-C", "target-feature=+crt-static"]
Loading