serde_hkx_features/convert/
mod.rs

1pub mod rayon;
2pub mod tokio;
3
4use crate::error::{Error, Result};
5use parse_display::{Display, FromStr};
6use std::{
7    ffi::OsStr,
8    path::{Path, PathBuf},
9};
10
11/// Output format
12///
13/// # Default
14/// `Amd64`
15#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Display, FromStr)]
17#[display(style = "camelCase")]
18#[non_exhaustive]
19pub enum OutFormat {
20    /// 64bit hkx
21    #[default]
22    Amd64,
23    /// 32bit hkx
24    Win32,
25    /// XML
26    Xml,
27
28    #[cfg(feature = "extra_fmt")]
29    /// json
30    Json,
31    #[cfg(feature = "extra_fmt")]
32    /// yaml
33    Toml,
34    #[cfg(feature = "extra_fmt")]
35    /// yaml
36    Yaml,
37}
38
39impl OutFormat {
40    /// Return the file extension corresponding to the format.
41    ///
42    /// # Examples
43    /// ```edition2021
44    /// use serde_hkx_features::convert::OutFormat;
45    ///
46    /// assert_eq!(OutFormat::Amd64.as_extension(), "hkx");
47    /// assert_eq!(OutFormat::Win32.as_extension(), "hkx");
48    /// assert_eq!(OutFormat::Xml.as_extension(), "xml");
49    /// ```
50    #[inline]
51    pub const fn as_extension(&self) -> &str {
52        match *self {
53            Self::Amd64 => "hkx",
54            Self::Win32 => "hkx",
55            Self::Xml => "xml",
56
57            #[cfg(feature = "extra_fmt")]
58            Self::Json => "json",
59            #[cfg(feature = "extra_fmt")]
60            Self::Toml => "toml",
61            #[cfg(feature = "extra_fmt")]
62            Self::Yaml => "yaml",
63        }
64    }
65
66    /// Return output format from input path.
67    ///
68    /// # Examples
69    /// ```edition2021
70    /// use serde_hkx_features::convert::OutFormat;
71    ///
72    /// assert_eq!(OutFormat::from_input("example.hkx").unwrap(), OutFormat::Xml);
73    /// assert_eq!(OutFormat::from_input("example.xml").unwrap(), OutFormat::Amd64);
74    /// ```
75    ///
76    /// When enable `extra_fmt` feature.
77    /// - `json`, `yaml` -> `Self::Amd64`
78    ///
79    /// # Errors
80    /// Unknown extension.
81    #[inline]
82    pub fn from_input<P>(path: P) -> Result<Self>
83    where
84        P: AsRef<Path>,
85    {
86        let path = path.as_ref();
87        let ext = path.extension().ok_or(Error::UnsupportedExtensionPath {
88            path: path.to_path_buf(),
89        })?;
90
91        Ok(match ext {
92            ext if ext.eq_ignore_ascii_case("hkx") => Self::Xml,
93            ext if ext.eq_ignore_ascii_case("xml") => Self::Amd64,
94
95            #[cfg(feature = "extra_fmt")]
96            ext if ext.eq_ignore_ascii_case("json") => Self::Amd64,
97            #[cfg(feature = "extra_fmt")]
98            ext if ext.eq_ignore_ascii_case("toml") => Self::Amd64,
99            #[cfg(feature = "extra_fmt")]
100            ext if ext.eq_ignore_ascii_case("yaml") || ext.eq_ignore_ascii_case("yml") => {
101                Self::Amd64
102            }
103            _ => {
104                return Err(Error::UnsupportedExtensionPath {
105                    path: path.to_path_buf(),
106                });
107            }
108        })
109    }
110
111    /// Determine format from extension.
112    ///
113    /// # Examples
114    /// ```edition2021
115    /// use serde_hkx_features::convert::OutFormat;
116    ///
117    /// assert_eq!(OutFormat::from_extension("hkx").unwrap(), OutFormat::Amd64);
118    /// assert_eq!(OutFormat::from_extension("xml").unwrap(), OutFormat::Xml);
119    /// ```
120    ///
121    /// When enable `extra_fmt` feature.
122    /// - `json` -> `Self::Json`
123    /// - `toml` -> `Self::Toml`
124    /// - `yaml` -> `Self::Yaml`
125    ///
126    /// # Errors
127    /// Unknown extension.
128    #[inline]
129    pub fn from_extension<S>(ext: S) -> Result<Self>
130    where
131        S: AsRef<OsStr>,
132    {
133        let ext = ext.as_ref();
134        Ok(match ext {
135            ext if ext.eq_ignore_ascii_case("hkx") => Self::Amd64,
136            ext if ext.eq_ignore_ascii_case("xml") => Self::Xml,
137
138            #[cfg(feature = "extra_fmt")]
139            ext if ext.eq_ignore_ascii_case("json") => Self::Json,
140            #[cfg(feature = "extra_fmt")]
141            ext if ext.eq_ignore_ascii_case("toml") => Self::Toml,
142            #[cfg(feature = "extra_fmt")]
143            ext if ext.eq_ignore_ascii_case("yaml") || ext.eq_ignore_ascii_case("yml") => {
144                Self::Yaml
145            }
146            _ => {
147                return Err(Error::UnsupportedExtension {
148                    ext: ext.to_string_lossy().to_string(),
149                });
150            }
151        })
152    }
153}
154
155////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
156// tokio & rayon common code
157
158fn get_output_path<D, I, O>(
159    input_dir: D,
160    input: I,
161    output_dir: &Option<O>,
162    format: OutFormat,
163) -> Option<PathBuf>
164where
165    D: AsRef<Path>,
166    I: AsRef<Path>,
167    O: AsRef<Path>,
168{
169    let input = input.as_ref();
170    let input_dir = input_dir.as_ref();
171
172    match output_dir {
173        Some(output_dir) => {
174            let input_inner_dir = input.strip_prefix(input_dir).ok()?;
175            let mut output = output_dir.as_ref().join(input_inner_dir);
176            output.set_extension(format.as_extension());
177            Some(output)
178        }
179        None => None,
180    }
181}
182
183fn filter_supported_files(entry: &jwalk::DirEntry<((), ())>) -> bool {
184    let path = entry.path();
185
186    if !path.is_file() {
187        return false;
188    }
189
190    path.extension()
191        .and_then(|ext| ext.to_str())
192        .is_some_and(|ext| {
193            if OutFormat::from_extension(ext).is_err() {
194                #[cfg(feature = "tracing")]
195                tracing::info!("Skip this unsupported extension: {}", path.display());
196                false
197            } else {
198                true
199            }
200        })
201}
202
203fn get_supported_files(input_dir: &Path) -> Vec<PathBuf> {
204    jwalk::WalkDir::new(input_dir)
205        .into_iter()
206        .filter_map(|entry| entry.ok())
207        .filter(filter_supported_files)
208        .map(|entry| entry.path())
209        .collect()
210}
211
212fn process_serde<I>(in_bytes: &Vec<u8>, input: I, format: OutFormat) -> Result<Vec<u8>>
213where
214    I: AsRef<Path>,
215{
216    let input = input.as_ref();
217    let mut string = String::new();
218
219    // Deserialize
220    let mut classes = {
221        #[cfg(not(feature = "extra_fmt"))]
222        {
223            crate::serde::de::deserialize(in_bytes, &mut string, input)?
224        }
225        #[cfg(feature = "extra_fmt")]
226        {
227            crate::serde_extra::de::deserialize(in_bytes, &mut string, input)?
228        }
229    };
230
231    // Serialize
232    let out_bytes = match format {
233        OutFormat::Amd64 | OutFormat::Win32 | OutFormat::Xml => {
234            crate::serde::ser::to_bytes(input, format, &mut classes)?
235        }
236        #[cfg(feature = "extra_fmt")]
237        OutFormat::Json | OutFormat::Toml | OutFormat::Yaml => {
238            let mut classes = crate::types_wrapper::ClassPtrMap::from_class_map(classes);
239            crate::serde_extra::ser::to_bytes(input, format, &mut classes)?
240        }
241    };
242
243    Ok(out_bytes)
244}