Skip to main content

ferrous_opencc/
config.rs

1use std::{
2    fs::File,
3    io::BufReader,
4    path::{
5        Path,
6        PathBuf,
7    },
8    str::FromStr,
9};
10
11use serde::{
12    Deserialize,
13    Serialize,
14};
15
16use crate::error::{
17    OpenCCError,
18    Result,
19};
20
21/// Top-level JSON configuration structure
22#[derive(Serialize, Deserialize, Debug)]
23pub struct Config {
24    /// Name of the conversion configuration
25    pub name: String,
26    /// Chain of conversion steps
27    pub conversion_chain: Vec<ConversionNodeConfig>,
28
29    /// Directory where the configuration file is located
30    #[serde(skip)]
31    directory: PathBuf,
32}
33
34/// All built-in `OpenCC` configurations
35#[repr(i32)]
36#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
37pub enum BuiltinConfig {
38    /// Simplified to Traditional
39    #[cfg(feature = "s2t-conversion")]
40    S2t = 0,
41    /// Traditional to Simplified
42    #[cfg(feature = "t2s-conversion")]
43    T2s = 1,
44    /// Simplified to Traditional (Taiwan)
45    #[cfg(feature = "s2t-conversion")]
46    S2tw = 2,
47    /// Traditional (Taiwan) to Simplified
48    #[cfg(feature = "t2s-conversion")]
49    Tw2s = 3,
50    /// Simplified to Traditional (Hong Kong)
51    #[cfg(feature = "s2t-conversion")]
52    S2hk = 4,
53    /// Traditional (Hong Kong) to Simplified
54    #[cfg(feature = "t2s-conversion")]
55    Hk2s = 5,
56    /// Simplified to Traditional (Taiwan) (including vocabulary conversion)
57    #[cfg(feature = "s2t-conversion")]
58    S2twp = 6,
59    /// Traditional (Taiwan) (including vocabulary conversion) to Simplified
60    #[cfg(feature = "t2s-conversion")]
61    Tw2sp = 7,
62    /// Traditional to Traditional (Taiwan)
63    #[cfg(feature = "t2s-conversion")]
64    T2tw = 8,
65    /// Traditional (Taiwan) to Traditional
66    #[cfg(feature = "s2t-conversion")]
67    Tw2t = 9,
68    /// Traditional to Traditional (Hong Kong)
69    #[cfg(feature = "s2t-conversion")]
70    T2hk = 10,
71    /// Traditional (Hong Kong) to Traditional
72    #[cfg(feature = "t2s-conversion")]
73    Hk2t = 11,
74    /// Japanese Shinjitai to Traditional
75    #[cfg(feature = "japanese-conversion")]
76    Jp2t = 12,
77    /// Traditional to Japanese Shinjitai
78    #[cfg(feature = "japanese-conversion")]
79    T2jp = 13,
80}
81
82impl BuiltinConfig {
83    /// Converts the enum variant to the corresponding filename string
84    #[must_use]
85    pub const fn to_filename(&self) -> &'static str {
86        match self {
87            #[cfg(feature = "s2t-conversion")]
88            Self::S2t => "s2t.json",
89            #[cfg(feature = "t2s-conversion")]
90            Self::T2s => "t2s.json",
91            #[cfg(feature = "s2t-conversion")]
92            Self::S2tw => "s2tw.json",
93            #[cfg(feature = "t2s-conversion")]
94            Self::Tw2s => "tw2s.json",
95            #[cfg(feature = "s2t-conversion")]
96            Self::S2hk => "s2hk.json",
97            #[cfg(feature = "t2s-conversion")]
98            Self::Hk2s => "hk2s.json",
99            #[cfg(feature = "s2t-conversion")]
100            Self::S2twp => "s2twp.json",
101            #[cfg(feature = "t2s-conversion")]
102            Self::Tw2sp => "tw2sp.json",
103            #[cfg(feature = "t2s-conversion")]
104            Self::T2tw => "t2tw.json",
105            #[cfg(feature = "s2t-conversion")]
106            Self::Tw2t => "tw2t.json",
107            #[cfg(feature = "s2t-conversion")]
108            Self::T2hk => "t2hk.json",
109            #[cfg(feature = "t2s-conversion")]
110            Self::Hk2t => "hk2t.json",
111            #[cfg(feature = "japanese-conversion")]
112            Self::Jp2t => "jp2t.json",
113            #[cfg(feature = "japanese-conversion")]
114            Self::T2jp => "t2jp.json",
115        }
116    }
117
118    /// Converts a filename string to the corresponding enum variant
119    pub fn from_filename(filename: &str) -> Result<Self> {
120        match filename {
121            #[cfg(feature = "s2t-conversion")]
122            "s2t.json" => Ok(Self::S2t),
123            #[cfg(feature = "t2s-conversion")]
124            "t2s.json" => Ok(Self::T2s),
125            #[cfg(feature = "s2t-conversion")]
126            "s2tw.json" => Ok(Self::S2tw),
127            #[cfg(feature = "t2s-conversion")]
128            "tw2s.json" => Ok(Self::Tw2s),
129            #[cfg(feature = "s2t-conversion")]
130            "s2hk.json" => Ok(Self::S2hk),
131            #[cfg(feature = "t2s-conversion")]
132            "hk2s.json" => Ok(Self::Hk2s),
133            #[cfg(feature = "s2t-conversion")]
134            "s2twp.json" => Ok(Self::S2twp),
135            #[cfg(feature = "t2s-conversion")]
136            "tw2sp.json" => Ok(Self::Tw2sp),
137            #[cfg(feature = "t2s-conversion")]
138            "t2tw.json" => Ok(Self::T2tw),
139            #[cfg(feature = "s2t-conversion")]
140            "tw2t.json" => Ok(Self::Tw2t),
141            #[cfg(feature = "s2t-conversion")]
142            "t2hk.json" => Ok(Self::T2hk),
143            #[cfg(feature = "t2s-conversion")]
144            "hk2t.json" => Ok(Self::Hk2t),
145            #[cfg(feature = "japanese-conversion")]
146            "jp2t.json" => Ok(Self::Jp2t),
147            #[cfg(feature = "japanese-conversion")]
148            "t2jp.json" => Ok(Self::T2jp),
149            _ => Err(OpenCCError::ConfigNotFound(filename.to_string())),
150        }
151    }
152}
153
154/// A node in the conversion chain
155///
156/// Each node corresponds to a dictionary-based conversion step
157#[derive(Serialize, Deserialize, Debug)]
158pub struct ConversionNodeConfig {
159    /// The dictionary to use for this conversion step
160    pub dict: DictConfig,
161}
162
163/// Represents a dictionary configuration, which can be a single dictionary file or a group of
164/// dictionaries
165#[derive(Serialize, Deserialize, Debug)]
166pub struct DictConfig {
167    /// Dictionary type, e.g., "text" or "group"
168    #[serde(rename = "type")]
169    pub dict_type: String,
170    /// Dictionary filename (for `type: "text"`)
171    pub file: Option<String>,
172    /// List of sub-dictionaries (for `type: "group"`)
173    pub dicts: Option<Vec<Self>>,
174}
175
176impl Config {
177    /// Loads and parses configuration from a JSON file
178    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
179        let path = path.as_ref();
180        let file = File::open(path)
181            .map_err(|e| OpenCCError::FileNotFound(format!("{}: {}", path.display(), e)))?;
182        let reader = BufReader::new(file);
183        let mut config: Self = serde_json::from_reader(reader)?;
184
185        config.directory = path.parent().unwrap_or_else(|| Path::new("")).to_path_buf();
186
187        Ok(config)
188    }
189
190    /// Gets the directory where the configuration file is located
191    #[must_use]
192    pub fn get_config_directory(&self) -> &Path {
193        &self.directory
194    }
195}
196
197impl FromStr for Config {
198    type Err = OpenCCError;
199
200    fn from_str(s: &str) -> Result<Self> {
201        let config: Self = serde_json::from_str(s)?;
202        Ok(config)
203    }
204}