use super::*;
pub fn first_path_from_str(s: &str) -> Result<PathBuf> {
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"[a-zA-Z0-9-_.\/]+\/[a-zA-Z0-9-_.]+").unwrap());
let path_str = RE.find(s).context("No path found in string")?.as_str();
Ok(PathBuf::from(path_str))
}
pub fn take_lines_with_failed_jobs(output: String) -> Vec<String> {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"X.*ID [0-9]*\)").unwrap());
RE.find_iter(&output)
.map(|m| m.as_str().to_owned())
.collect()
}
pub fn id_from_job_lines(lines: &[String]) -> Vec<String> {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"ID (?<JOB_ID>[0-9]*)").unwrap());
lines
.iter()
.map(|line| {
RE.captures(line)
.unwrap_or_else(|| {
panic!("Expected a line with a Job ID, but no ID found in line: {line}")
})
.name("JOB_ID")
.expect("Expected a Job ID")
.as_str()
.to_owned()
})
.collect()
}
pub fn remove_timestamps_and_ids(text: &str) -> borrow::Cow<str> {
static RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?x)
# Timestamps like YYYY-MM-DD HH:MM:SS
([0-9]{4}-[0-9]{2}-[0-9]{2}\x20[0-9]{2}:[0-9]{2}:[0-9]{2})
|
# IDs like 21442749267 but only if they are preceded and followed by non-letter characters
(?:[^[a-zA-Z]])([0-9]{10,11})(?:[^[a-zA-Z]])
",
)
.unwrap()
});
RE.replace_all(text, "")
}
pub fn remove_non_ascii(text: &str) -> borrow::Cow<str> {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[^\x00-\x7F]+").unwrap());
RE.replace_all(text, "")
}
pub fn remove_ansi_codes(text: &str) -> borrow::Cow<str> {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\x1b\[[;\d]*[A-Za-z]").unwrap());
RE.replace_all(text, "")
}
pub fn remove_timestamp_prefixes(log: &str) -> borrow::Cow<str> {
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"([\r\n]*)\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{7}Z\s").unwrap());
RE.replace_all(log, "$1")
}
pub fn first_abs_path_from_str(s: &str) -> Result<PathBuf> {
let start = s.find('/').context("Path not found, no '/' in string")?;
let path = PathBuf::from(&s[start..]);
Ok(path)
}
pub fn ensure_https_prefix(url: &mut String) {
if url.starts_with("https://") {
return;
}
url.insert_str(0, "https://");
}
pub fn canonicalize_repo_url(repo: &str, host: &str) -> String {
let host = if host.contains('.') {
host.to_string()
} else {
format!("{host}.com")
};
let canonical_prefix: String = format!("https://{host}/");
if repo.starts_with("https://") {
if repo.starts_with(&canonical_prefix) {
repo.to_string()
} else {
repo.replace("https://", &canonical_prefix)
}
} else if repo.starts_with(&format!("{host}/")) {
repo.replace(&format!("{host}/"), &canonical_prefix)
} else {
format!("{canonical_prefix}{repo}")
}
}
pub fn repo_to_owner_repo_fragments(repo_url: &str) -> Result<(String, String)> {
let parts: Vec<&str> = repo_url.split('/').collect();
let repo_and_owner = parts.into_iter().rev().take(2).collect::<Vec<&str>>();
if repo_and_owner.len() != 2
|| repo_and_owner
.iter()
.any(|s| s.is_empty() || s.contains(' ') || s.contains('.'))
{
bail!("Could not parse owner and repo from URL: {repo_url}");
}
let (repo, owner) = (repo_and_owner[0], repo_and_owner[1]);
Ok((owner.to_string(), repo.to_string()))
}
pub fn issue_text_similarity(issue_body: &str, other_issues: &[String]) -> usize {
let issue_body_without_timestamps = remove_timestamps_and_ids(issue_body);
let smallest_distance = other_issues
.iter()
.map(|other_issue_body| {
distance::levenshtein(
&issue_body_without_timestamps,
&remove_timestamps_and_ids(other_issue_body),
)
})
.min()
.unwrap_or(usize::MAX);
smallest_distance
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_absolute_path_from_str() {
let test_str = r#" ERROR: Logfile of failure stored in: /app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21616"#;
let path = first_abs_path_from_str(test_str).unwrap();
assert_eq!(
path,
PathBuf::from("/app/yocto/build/tmp/work/x86_64-linux/sqlite3-native/3.43.2/temp/log.do_fetch.21616")
);
}
#[test]
pub fn test_canonicalize_repo_url() {
let repo = "luftkode/distro-template";
let canonicalized = canonicalize_repo_url(repo, "github.com");
assert_eq!(canonicalized, "https://github.com/luftkode/distro-template");
}
#[test]
pub fn test_remove_timestamps_and_ids() {
let test_str = "ID 8072883145 ";
let modified = remove_timestamps_and_ids(test_str);
assert_eq!(modified, "ID");
}
#[test]
pub fn test_remove_timestamps_and_ids_log_text() {
const LOG_TEXT: &'static str = r#"**Run ID**: 8072883145 [LINK TO RUN](https://github.com/luftkode/distro-template/actions/runs/8072883145)
**1 job failed:**
- **`Test template xilinx`**
### `Test template xilinx` (ID 22055505284)
**Step failed:** `📦 Build yocto image`
\
**Log:** https://github.com/luftkode/distro-template/actions/runs/8072883145/job/22055505284
"#;
const EXPECTED_MODIFIED: &'static str = r#"**Run ID**:[LINK TO RUN](https://github.com/luftkode/distro-template/actions/runs
**1 job failed:**
- **`Test template xilinx`**
### `Test template xilinx` (ID
**Step failed:** `📦 Build yocto image`
\
**Log:** https://github.com/luftkode/distro-template/actions/runsjob "#;
let modified = remove_timestamps_and_ids(LOG_TEXT);
assert_eq!(
modified, EXPECTED_MODIFIED,
"Expected: {EXPECTED_MODIFIED}\nGot: {modified}"
);
}
}