1use std::{
2 fs::File,
3 io::Read,
4 path::{Path, PathBuf},
5};
6
7use encoding_rs::Encoding;
8use encoding_rs_io::DecodeReaderBytesBuilder;
9use serde::{Deserialize, Serialize};
10
11pub mod source_location;
12#[derive(Clone, Copy, Debug)]
14pub enum SourceType {
15 Text,
17
18 Xml,
20
21 Unknown,
23}
24
25pub trait SourceContainer: Sync + Send {
28 fn load_source(&self, encoding: Option<&'static Encoding>) -> Result<SourceCode, String>;
30 fn get_location(&self) -> Option<&Path>;
32
33 fn get_type(&self) -> SourceType {
36 if let Some(ext) = self.get_location().and_then(|it| it.extension()) {
37 match ext.to_str() {
38 Some("o") | Some("so") | Some("exe") => SourceType::Unknown,
39 Some("cfc") | Some("fbd") | Some("xml") => SourceType::Xml,
41 _ => SourceType::Text,
42 }
43 } else {
44 SourceType::Text
45 }
46 }
47
48 fn get_location_str(&self) -> &'static str {
50 let s = self
51 .get_location()
52 .map(|it| it.to_string_lossy())
53 .map(|it| it.to_string())
54 .unwrap_or_else(|| "<internal>".into());
55 Box::leak(s.into_boxed_str())
56 }
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize)]
61pub struct SourceCode {
62 pub source: String,
64 pub path: Option<PathBuf>,
66}
67
68impl SourceContainer for SourceCode {
70 fn load_source(&self, _: Option<&'static Encoding>) -> Result<SourceCode, String> {
71 Ok(self.clone())
72 }
73
74 fn get_location(&self) -> Option<&Path> {
75 self.path.as_deref()
76 }
77}
78
79pub type BuildDescriptionSource = SourceCode;
80
81pub trait SourceCodeFactory {
83 fn create_source(self, path: impl Into<PathBuf>) -> SourceCode;
84}
85
86impl SourceCodeFactory for &str {
87 fn create_source(self, path: impl Into<PathBuf>) -> SourceCode {
88 SourceCode::new(self, path)
89 }
90}
91
92impl<T: AsRef<Path> + Sync + Send> SourceContainer for T {
93 fn load_source(&self, encoding: Option<&'static Encoding>) -> Result<SourceCode, String> {
94 let source_type = self.get_type();
95 if matches!(source_type, SourceType::Text | SourceType::Xml) {
96 let mut file = File::open(self).map_err(|err| err.to_string())?;
97 let source = create_source_code(&mut file, encoding)?;
98
99 Ok(SourceCode { source, path: Some(self.as_ref().to_owned()) })
100 } else {
101 Err(format!("{} is not a source file", &self.as_ref().to_string_lossy()))
102 }
103 }
104
105 fn get_location(&self) -> Option<&Path> {
106 Some(self.as_ref())
107 }
108}
109
110pub fn create_source_code<T: Read>(
111 reader: &mut T,
112 encoding: Option<&'static Encoding>,
113) -> Result<String, String> {
114 let mut buffer = String::new();
115 let mut decoder = DecodeReaderBytesBuilder::new().encoding(encoding).build(reader);
116 decoder.read_to_string(&mut buffer).map_err(|err| format!("{err}"))?;
117 Ok(buffer)
118}
119
120impl From<&str> for SourceCode {
121 fn from(src: &str) -> Self {
122 SourceCode { source: src.into(), path: Some("<internal>".into()) }
123 }
124}
125
126impl From<String> for SourceCode {
127 fn from(source: String) -> Self {
128 SourceCode { source, path: Some("<internal>".into()) }
129 }
130}
131
132impl SourceCode {
133 pub fn new(source: impl Into<String>, path: impl Into<PathBuf>) -> Self {
134 SourceCode { source: source.into(), path: Some(path.into()) }
135 }
136
137 pub fn with_path(mut self, name: impl Into<PathBuf>) -> Self {
138 self.path = Some(name.into());
139 self
140 }
141}
142
143pub trait Compilable {
144 type T: SourceContainer;
145 fn containers(self) -> Vec<Self::T>;
146}
147
148impl Compilable for &str {
149 type T = SourceCode;
150 fn containers(self) -> Vec<Self::T> {
151 let code = Self::T::from(self);
152 vec![code]
153 }
154}
155
156impl Compilable for String {
157 type T = SourceCode;
158 fn containers(self) -> Vec<Self::T> {
159 let code = self.into();
160 vec![code]
161 }
162}
163
164impl<S: SourceContainer> Compilable for Vec<S> {
165 type T = S;
166 fn containers(self) -> Vec<Self::T> {
167 self
168 }
169}
170
171impl Compilable for SourceCode {
172 type T = Self;
173
174 fn containers(self) -> Vec<Self::T> {
175 vec![self]
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use crate::create_source_code;
182
183 #[test]
184 fn windows_encoded_file_content_read() {
185 let expected = r"PROGRAM ä
186(* Cöment *)
187END_PROGRAM
188";
189 let mut source = &b"\x50\x52\x4f\x47\x52\x41\x4d\x20\xe4\x0a\x28\x2a\x20\x43\xf6\x6d\x65\x6e\x74\x20\x2a\x29\x0a\x45\x4e\x44\x5f\x50\x52\x4f\x47\x52\x41\x4d\x0a"[..];
190 let source = create_source_code(&mut source, Some(encoding_rs::WINDOWS_1252)).unwrap();
192
193 assert_eq!(expected, &source);
194 }
195
196 #[test]
197 fn utf_16_encoded_file_content_read() {
198 let expected = r"PROGRAM ä
199(* Cömment *)
200END_PROGRAM
201";
202
203 let mut source = &b"\xff\xfe\x50\x00\x52\x00\x4f\x00\x47\x00\x52\x00\x41\x00\x4d\x00\x20\x00\xe4\x00\x0a\x00\x28\x00\x2a\x00\x20\x00\x43\x00\xf6\x00\x6d\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x20\x00\x2a\x00\x29\x00\x0a\x00\x45\x00\x4e\x00\x44\x00\x5f\x00\x50\x00\x52\x00\x4f\x00\x47\x00\x52\x00\x41\x00\x4d\x00\x0a\x00" [..];
204
205 let source = create_source_code(&mut source, None).unwrap();
206 assert_eq!(expected, &source);
207 }
208
209 #[test]
210 fn utf_8_encoded_file_content_read() {
211 let expected = r"PROGRAM ä
212(* Cöment *)
213END_PROGRAM
214";
215
216 let mut source = &b"\x50\x52\x4f\x47\x52\x41\x4d\x20\xc3\xa4\x0a\x28\x2a\x20\x43\xc3\xb6\x6d\x65\x6e\x74\x20\x2a\x29\x0a\x45\x4e\x44\x5f\x50\x52\x4f\x47\x52\x41\x4d\x0a" [..];
217 let source = create_source_code(&mut source, None).unwrap();
218 assert_eq!(expected, &source);
219 }
220}