use crate::models::RulesetContent;
use crate::utils::string::{find_str, starts_with, trim};
use crate::Settings;
use lazy_static::lazy_static;
use log::warn;
use serde_yaml::Value as YamlValue;
use std::collections::HashSet;
use super::common::transform_rule_to_common;
use super::convert_ruleset::convert_ruleset;
lazy_static! {
static ref CLASH_RULE_TYPES: HashSet<&'static str> = {
let mut types = HashSet::new();
types.insert("DOMAIN");
types.insert("DOMAIN-SUFFIX");
types.insert("DOMAIN-KEYWORD");
types.insert("IP-CIDR");
types.insert("SRC-IP-CIDR");
types.insert("GEOIP");
types.insert("MATCH");
types.insert("FINAL");
types.insert("IP-CIDR6");
types.insert("SRC-PORT");
types.insert("DST-PORT");
types.insert("PROCESS-NAME");
types
};
}
pub fn ruleset_to_clash_str(
base_rule: &YamlValue,
ruleset_content_array: &[RulesetContent],
overwrite_original_rules: bool,
new_field_name: bool,
) -> String {
let settings = Settings::current();
let field_name = if new_field_name { "rules" } else { "Rule" };
let mut output_content = format!("\n{}:\n", field_name);
let mut total_rules = 0;
if !overwrite_original_rules {
if let Some(rules) = base_rule.get(field_name) {
if let Some(rules_array) = rules.as_sequence() {
for rule in rules_array {
if let Some(rule_str) = rule.as_str() {
output_content.push_str(&format!(" - {}\n", rule_str));
}
}
}
}
}
let max_allowed_rules = settings.max_allowed_rules;
for ruleset in ruleset_content_array {
if max_allowed_rules > 0 && total_rules >= max_allowed_rules {
break;
}
let rule_group = &ruleset.group;
let retrieved_rules = ruleset.get_rule_content();
if retrieved_rules.is_empty() {
warn!(
"Failed to fetch ruleset or ruleset is empty: '{}'!",
ruleset.rule_path
);
continue;
}
if starts_with(&retrieved_rules, "[]") {
let mut rule_line = retrieved_rules[2..].to_string();
if starts_with(&rule_line, "FINAL") {
rule_line = rule_line.replacen("FINAL", "MATCH", 1);
}
let transformed = transform_rule_to_common(&rule_line, rule_group, false);
output_content.push_str(&format!(" - {}\n", transformed));
total_rules += 1;
continue;
}
let processed_rules = convert_ruleset(&retrieved_rules, ruleset.rule_type);
let _line_break = if processed_rules.contains("\r\n") {
"\r\n"
} else {
"\n"
};
for line in processed_rules.lines() {
if max_allowed_rules > 0 && total_rules >= max_allowed_rules {
break;
}
let mut str_line = line.trim().to_string();
let line_size = str_line.len();
if line_size == 0
|| (line_size >= 1 && (str_line.starts_with(';') || str_line.starts_with('#')))
|| (line_size >= 2 && str_line.starts_with("//"))
{
continue;
}
if !CLASH_RULE_TYPES
.iter()
.any(|&rule_type| starts_with(&str_line, rule_type))
{
continue;
}
if let Some(comment_pos) = find_str(&str_line, "//") {
str_line = str_line[..comment_pos].to_string();
str_line = trim(&str_line).to_string();
}
let transformed = transform_rule_to_common(&str_line, rule_group, false);
output_content.push_str(&format!(" - {}\n", transformed));
total_rules += 1;
}
}
output_content
}