lang_interpreter/interpreter/
platform.rs

1#[cfg(feature = "wasm-platform-api")]
2#[doc(hidden)]
3pub mod wasm;
4#[cfg(feature = "wasm-platform-api")]
5#[doc(inline)]
6pub use wasm::WASMPlatformAPI;
7
8use std::collections::HashMap;
9use std::ffi::OsString;
10use std::fmt::Debug;
11use std::{fs, io};
12use std::fs::File;
13use std::io::{Error, Read, Write};
14use std::path::{Path, PathBuf};
15use crate::interpreter::data::function::native::NativeError;
16
17/// This trait is used to abstract some io functionality
18pub trait PlatformAPI: Debug {
19    /// Returns all files inside the folder located at `lang_path`
20    ///
21    /// # Arguments
22    ///
23    /// * `lang_path` - Path to the folder
24    fn get_lang_files(&self, lang_path: &Path) -> Result<Vec<PathBuf>, Error>;
25
26    /// Returns the canonical path of the file located at `lang_file`
27    ///
28    /// # Arguments
29    ///
30    /// * `lang_file` - Path to the file
31    fn get_lang_path(&self, lang_file: &Path) -> Result<PathBuf, Error>;
32
33    /// Return the file name of the file located at `lang_file`
34    ///
35    /// # Arguments
36    ///
37    /// * `lang_file` - Path to the file
38    fn get_lang_file_name(&self, lang_file: &Path) -> Option<OsString>;
39
40    /// Returns a `Box<u8>` for the file located at `lang_file`
41    ///
42    /// # Arguments
43    ///
44    /// * `lang_file` - Path to the file
45    fn get_lang_reader(&self, lang_file: &Path) -> Result<Box<[u8]>, Error>;
46
47    /// Writes a translation file
48    ///
49    /// # Arguments
50    ///
51    /// * `lang_file` - Path to the file
52    /// * `translation_map` - The map of all translations
53    fn write_lang_file(&self, lang_file: &Path, translation_map: HashMap<String, String>) -> Result<(), Error>;
54
55    /// Returns the value inputted by the user
56    ///
57    /// # Arguments
58    ///
59    /// * `text`: The text prompt to be shown to the user
60    fn show_input_dialog(&self, text: &str) -> Result<String, NativeError>;
61
62    /// Prints to standard output without newline
63    ///
64    /// # Arguments
65    ///
66    /// * `text`: The text to be outputted
67    fn print(&mut self, text: &str) {
68        print!("{text}");
69        let _ = io::stdout().flush();
70    }
71
72    /// Prints to standard output with newline
73    ///
74    /// # Arguments
75    ///
76    /// * `text`: The text to be outputted
77    fn println(&mut self, text: &str) {
78        println!("{text}");
79    }
80
81    /// Prints to error output without newline
82    ///
83    /// # Arguments
84    ///
85    /// * `text`: The text to be outputted
86    fn print_error(&mut self, text: &str) {
87        eprint!("{text}");
88        let _ = io::stderr().flush();
89    }
90
91    /// Prints to error output with newline
92    ///
93    /// # Arguments
94    ///
95    /// * `text`: The text to be outputted
96    fn println_error(&mut self, text: &str) {
97        eprintln!("{text}");
98    }
99}
100
101/// This uses standard io operations.
102///
103/// The [show_input_dialog](DefaultPlatformAPI::show_input_dialog) method is not implemented and will always return an [Err],
104/// because showing a dialog requires using a platform-dependent GUI API.
105#[derive(Debug)]
106pub struct DefaultPlatformAPI;
107
108impl DefaultPlatformAPI {
109    pub fn new() -> Self {
110        Self
111    }
112}
113
114impl Default for DefaultPlatformAPI {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120impl PlatformAPI for DefaultPlatformAPI {
121    fn get_lang_files(&self, lang_path: &Path) -> Result<Vec<PathBuf>, Error> {
122        let files = fs::read_dir(lang_path)?;
123
124        let mut lang_files = Vec::new();
125
126        for file in files {
127            let file = file?;
128            let file_type = file.file_type()?;
129            if !file_type.is_dir() {
130                let file_name = file.file_name();
131                if let Some(file_name) = file_name.to_str()
132                    && file_name.to_ascii_lowercase().ends_with(".lang") {
133                        lang_files.push(file.path());
134                    }
135            }
136        }
137
138        Ok(lang_files)
139    }
140
141    fn get_lang_path(&self, lang_file: &Path) -> Result<PathBuf, Error> {
142        let path = lang_file;
143        let mut path = path.parent().unwrap_or(Path::new(""));
144        if path == Path::new("") {
145            path = Path::new("./");
146        }
147
148        let canonical_path = path.canonicalize();
149        if let Ok(canonical_path) = canonical_path {
150            Ok(canonical_path)
151        }else {
152            std::path::absolute(path)
153        }
154    }
155
156    fn get_lang_file_name(&self, lang_file: &Path) -> Option<OsString> {
157        let path = lang_file;
158
159        path.file_name().map(|str| str.to_os_string())
160    }
161
162    fn get_lang_reader(&self, lang_file: &Path) -> Result<Box<[u8]>, Error> {
163        let path = lang_file;
164
165        let mut file = File::open(path)?;
166
167        let mut bytes = Vec::new();
168        file.read_to_end(&mut bytes)?;
169
170        Ok(bytes.into_boxed_slice())
171    }
172
173    fn write_lang_file(&self, lang_file: &Path, translation_map: HashMap<String, String>) -> Result<(), Error> {
174        let path = lang_file;
175
176        let mut file = File::create(path)?;
177
178        for (translation_key, translation_value) in translation_map {
179            //For multiline
180            let translation_value = translation_value.replace("\n", "\\n");
181
182            writeln!(file, "{translation_key} = {translation_value}")?;
183        }
184
185        Ok(())
186    }
187
188    /// This method is not implemented
189    ///
190    /// Trait doc: [show_input_dialog](PlatformAPI::show_input_dialog)
191    fn show_input_dialog(&self, _text: &str) -> Result<String, NativeError> {
192        Err(NativeError::new(
193            "Not Implemented",
194            None,
195        ))
196    }
197}