convertor 2.6.12

A profile converter for surge/clash.
Documentation
#![deny(unused, unused_variables)]

use crate::config::client_config::ProxyClient;
use crate::core::profile::Profile;
use crate::core::profile::policy::Policy;
use crate::core::profile::proxy::Proxy;
use crate::core::profile::proxy_group::ProxyGroup;
use crate::core::profile::rule::{ProviderRule, Rule};
use crate::core::profile::rule_provider::RuleProvider;
use crate::error::RenderError;
use std::fmt::Write;
use tracing::instrument;

pub mod clash_renderer;
pub mod surge_renderer;

type Result<T> = core::result::Result<T, RenderError>;

pub const INDENT: usize = 4;

pub trait Renderer {
    type PROFILE: Profile;

    fn client() -> ProxyClient;

    fn render_profile(profile: &Self::PROFILE) -> Result<String>;

    fn render_general(profile: &Self::PROFILE) -> Result<String>;

    #[instrument(skip_all)]
    fn render_proxies(proxies: &[Proxy]) -> Result<String> {
        Self::render_lines(proxies, Self::render_proxy)
    }

    fn render_proxy(proxy: &Proxy) -> Result<String>;

    #[instrument(skip_all)]
    fn render_proxy_groups(proxy_groups: &[ProxyGroup]) -> Result<String> {
        Self::render_lines(proxy_groups, Self::render_proxy_group)
    }

    fn render_proxy_group(proxy_group: &ProxyGroup) -> Result<String>;

    #[instrument(skip_all)]
    fn render_rules(rules: &[Rule]) -> Result<String> {
        Self::render_lines(rules, Self::render_rule)
    }

    fn render_rule(rule: &Rule) -> Result<String>;

    /// 适用于渲染规则集类型的规则,不包含注释
    fn render_rule_for_provider(rule: &Rule) -> Result<String>;

    #[instrument(skip_all)]
    fn render_provider_rules(rules: &[ProviderRule]) -> Result<String> {
        let mut output = String::new();
        match Self::client() {
            ProxyClient::Surge => {
                writeln!(output, "{}", Self::render_lines(rules, Self::render_provider_rule)?)?;
            }
            ProxyClient::Clash => {
                writeln!(output, "payload:")?;
                writeln!(output, "{}", Self::render_lines(rules, Self::render_provider_rule)?)?;
            }
        }
        Ok(output)
    }

    fn render_provider_rule(rule: &ProviderRule) -> Result<String>;

    fn render_policy(policy: &Policy) -> Result<String> {
        let mut output = String::new();
        write!(output, "{}", policy.name)?;
        if let Some(option) = &policy.option {
            write!(output, ",{option}")?;
        }
        Ok(output)
    }

    fn render_rule_providers(rule_providers: &[(String, RuleProvider)]) -> Result<String>;

    fn render_rule_provider(rule_provider: &(String, RuleProvider)) -> Result<String>;

    fn render_provider_name_for_policy(policy: &Policy) -> String;

    fn render_lines<T, F>(lines: impl IntoIterator<Item = T>, map: F) -> Result<String>
    where
        F: FnMut(T) -> Result<String>,
    {
        let output = lines
            .into_iter()
            .map(map)
            .map(|line| match Self::client() {
                ProxyClient::Surge => line,
                ProxyClient::Clash => line.map(Self::indent_line),
            })
            .collect::<Result<Vec<_>>>()?
            .join("\n");
        Ok(output)
    }

    fn indent_line(line: String) -> String {
        format!("{:indent$}{}", "", format_args!("- {}", line), indent = INDENT)
    }
}