///|
pub fn edit_distance(left : String, right : String) -> Int {
let prev = Array::make(right.length() + 1, 0)
let curr = Array::make(right.length() + 1, 0)
for j = 0; j <= right.length(); j = j + 1 {
prev[j] = j
}
for i = 1; i <= left.length(); i = i + 1 {
curr[0] = i
for j = 1; j <= right.length(); j = j + 1 {
let cost = if left[i - 1] == right[j - 1] { 0 } else { 1 }
curr[j] = min_int(
min_int(curr[j - 1] + 1, prev[j] + 1),
prev[j - 1] + cost,
)
}
for j = 0; j <= right.length(); j = j + 1 {
prev[j] = curr[j]
}
}
prev[right.length()]
}
///|
pub fn min_int(a : Int, b : Int) -> Int {
if a < b {
a
} else {
b
}
}
///|
pub fn suggest_directive(key : String) -> String? {
let known = [
"user-agent", "allow", "disallow", "crawl-delay", "sitemap", "host",
]
let mut best = ""
let mut score = 999
for item in known {
let distance = edit_distance(lower_ascii(key), item)
if distance < score {
score = distance
best = item
}
}
if score <= 3 {
Some(best)
} else {
None
}
}
///|
pub fn directive_suggestion_report(text : String) -> String {
let lines : Array[String] = []
for item in unknown_directives(text) {
match suggest_directive(item.key) {
Some(suggestion) =>
lines.push(
"line " + item.line.to_string() + ": did you mean " + suggestion + "?",
)
None =>
lines.push("line " + item.line.to_string() + ": unknown " + item.key)
}
}
if lines.is_empty() {
"ok"
} else {
join_lines(lines)
}
}
///|
pub fn rule_suggestion(text : String, agent : String, path : String) -> String {
let decision = decide(parse(text), agent, path)
if decision.matched_kind == "none" {
"no matching rule; add Allow or Disallow for " + path_parent(path)
} else if decision.allowed {
"allowed by " + decision.matched_pattern
} else {
"blocked by " + decision.matched_pattern
}
}
///|
pub fn diagnostics_report(
text : String,
agent : String,
paths : Array[String],
) -> String {
join_lines([
"directives:",
directive_suggestion_report(text),
"sensitive:",
sensitive_report(text, agent),
"coverage:",
coverage_report(text, agent, paths),
])
}