ferrous_opencc/
config.rs

1use crate::error::{OpenCCError, Result};
2use serde::{Deserialize, Serialize};
3use std::fs::File;
4use std::io::BufReader;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7
8/// 顶层的 JSON 配置结构
9#[derive(Serialize, Deserialize, Debug)]
10pub struct Config {
11    /// 转换配置的名称
12    pub name: String,
13    /// 转换步骤链
14    pub conversion_chain: Vec<ConversionNodeConfig>,
15
16    /// 配置文件所在的目录
17    #[serde(skip)]
18    directory: PathBuf,
19}
20
21/// 所有内置的 `OpenCC` 配置
22#[repr(i32)]
23#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
24pub enum BuiltinConfig {
25    /// 简体到繁体
26    #[cfg(feature = "s2t-conversion")]
27    S2t = 0,
28    /// 繁体到简体
29    #[cfg(feature = "t2s-conversion")]
30    T2s = 1,
31    /// 简体到台湾正体
32    #[cfg(feature = "s2t-conversion")]
33    S2tw = 2,
34    /// 台湾正体到简体
35    #[cfg(feature = "t2s-conversion")]
36    Tw2s = 3,
37    /// 简体到香港繁体
38    #[cfg(feature = "s2t-conversion")]
39    S2hk = 4,
40    /// 香港繁体到简体
41    #[cfg(feature = "t2s-conversion")]
42    Hk2s = 5,
43    /// 简体到台湾正体(包含词汇转换)
44    #[cfg(feature = "s2t-conversion")]
45    S2twp = 6,
46    /// 台湾正体(包含词汇转换)到简体
47    #[cfg(feature = "t2s-conversion")]
48    Tw2sp = 7,
49    /// 繁体到台湾正体
50    #[cfg(feature = "t2s-conversion")]
51    T2tw = 8,
52    /// 台湾正体到繁体
53    #[cfg(feature = "s2t-conversion")]
54    Tw2t = 9,
55    /// 繁体到香港繁体
56    #[cfg(feature = "s2t-conversion")]
57    T2hk = 10,
58    /// 香港繁体到繁体
59    #[cfg(feature = "t2s-conversion")]
60    Hk2t = 11,
61    /// 日语新字体到繁体
62    #[cfg(feature = "japanese-conversion")]
63    Jp2t = 12,
64    /// 繁体到日语新字体
65    #[cfg(feature = "japanese-conversion")]
66    T2jp = 13,
67}
68
69impl BuiltinConfig {
70    /// 将枚举成员转换为对应的文件名字符串
71    #[must_use]
72    pub const fn to_filename(&self) -> &'static str {
73        match self {
74            #[cfg(feature = "s2t-conversion")]
75            Self::S2t => "s2t.json",
76            #[cfg(feature = "t2s-conversion")]
77            Self::T2s => "t2s.json",
78            #[cfg(feature = "s2t-conversion")]
79            Self::S2tw => "s2tw.json",
80            #[cfg(feature = "t2s-conversion")]
81            Self::Tw2s => "tw2s.json",
82            #[cfg(feature = "s2t-conversion")]
83            Self::S2hk => "s2hk.json",
84            #[cfg(feature = "t2s-conversion")]
85            Self::Hk2s => "hk2s.json",
86            #[cfg(feature = "s2t-conversion")]
87            Self::S2twp => "s2twp.json",
88            #[cfg(feature = "t2s-conversion")]
89            Self::Tw2sp => "tw2sp.json",
90            #[cfg(feature = "t2s-conversion")]
91            Self::T2tw => "t2tw.json",
92            #[cfg(feature = "s2t-conversion")]
93            Self::Tw2t => "tw2t.json",
94            #[cfg(feature = "s2t-conversion")]
95            Self::T2hk => "t2hk.json",
96            #[cfg(feature = "t2s-conversion")]
97            Self::Hk2t => "hk2t.json",
98            #[cfg(feature = "japanese-conversion")]
99            Self::Jp2t => "jp2t.json",
100            #[cfg(feature = "japanese-conversion")]
101            Self::T2jp => "t2jp.json",
102        }
103    }
104
105    /// 从文件名字符串转换为对应的枚举成员
106    pub fn from_filename(filename: &str) -> Result<Self> {
107        match filename {
108            #[cfg(feature = "s2t-conversion")]
109            "s2t.json" => Ok(Self::S2t),
110            #[cfg(feature = "t2s-conversion")]
111            "t2s.json" => Ok(Self::T2s),
112            #[cfg(feature = "s2t-conversion")]
113            "s2tw.json" => Ok(Self::S2tw),
114            #[cfg(feature = "t2s-conversion")]
115            "tw2s.json" => Ok(Self::Tw2s),
116            #[cfg(feature = "s2t-conversion")]
117            "s2hk.json" => Ok(Self::S2hk),
118            #[cfg(feature = "t2s-conversion")]
119            "hk2s.json" => Ok(Self::Hk2s),
120            #[cfg(feature = "s2t-conversion")]
121            "s2twp.json" => Ok(Self::S2twp),
122            #[cfg(feature = "t2s-conversion")]
123            "tw2sp.json" => Ok(Self::Tw2sp),
124            #[cfg(feature = "t2s-conversion")]
125            "t2tw.json" => Ok(Self::T2tw),
126            #[cfg(feature = "s2t-conversion")]
127            "tw2t.json" => Ok(Self::Tw2t),
128            #[cfg(feature = "s2t-conversion")]
129            "t2hk.json" => Ok(Self::T2hk),
130            #[cfg(feature = "t2s-conversion")]
131            "hk2t.json" => Ok(Self::Hk2t),
132            #[cfg(feature = "japanese-conversion")]
133            "jp2t.json" => Ok(Self::Jp2t),
134            #[cfg(feature = "japanese-conversion")]
135            "t2jp.json" => Ok(Self::T2jp),
136            _ => Err(OpenCCError::ConfigNotFound(filename.to_string())),
137        }
138    }
139}
140
141/// 转换链中的一个节点
142///
143/// 每个节点对应一个基于词典的转换步骤
144#[derive(Serialize, Deserialize, Debug)]
145pub struct ConversionNodeConfig {
146    /// 此转换步骤要使用的词典
147    pub dict: DictConfig,
148}
149
150/// 代表一个词典配置,可以是一个单独的词典文件,也可以是一组词典
151#[derive(Serialize, Deserialize, Debug)]
152pub struct DictConfig {
153    /// 词典的类型,例如 "text" 或 "group"
154    #[serde(rename = "type")]
155    pub dict_type: String,
156    /// 词典文件名 (用于 `type: "text"`)
157    pub file: Option<String>,
158    /// 子词典列表 (用于 `type: "group"`)。
159    pub dicts: Option<Vec<Self>>,
160}
161
162impl Config {
163    /// 从 JSON 文件加载并解析配置
164    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
165        let path = path.as_ref();
166        let file = File::open(path)
167            .map_err(|e| OpenCCError::FileNotFound(format!("{}: {}", path.display(), e)))?;
168        let reader = BufReader::new(file);
169        let mut config: Self = serde_json::from_reader(reader)?;
170
171        // 保存配置文件的父目录
172        config.directory = path.parent().unwrap_or_else(|| Path::new("")).to_path_buf();
173
174        Ok(config)
175    }
176
177    /// 获取配置文件所在的目录
178    #[must_use]
179    pub fn get_config_directory(&self) -> &Path {
180        &self.directory
181    }
182}
183
184impl FromStr for Config {
185    type Err = OpenCCError;
186
187    fn from_str(s: &str) -> Result<Self> {
188        let config: Self = serde_json::from_str(s)?;
189        Ok(config)
190    }
191}