1use std::{
2 cell::RefCell,
3 collections::BTreeMap,
4 io::{self, Read, Seek, Write},
5 iter::once,
6 ops::Add,
7 rc::Rc,
8 str::from_utf8,
9 time::Duration,
10 vec,
11};
12
13use io_trait::{File, Io};
14
15#[derive(Debug, Clone)]
16pub struct Metadata {
17 len: u64,
18 is_dir: bool,
19}
20
21impl io_trait::Metadata for Metadata {
22 fn len(&self) -> u64 {
23 self.len
24 }
25 fn is_dir(&self) -> bool {
26 self.is_dir
27 }
28}
29
30#[derive(Debug, Default, Clone)]
31pub struct VecRef(Rc<RefCell<Vec<u8>>>);
32
33impl VecRef {
34 pub fn to_stdout(&self) -> String {
35 let mut result = Vec::default();
36 let mut i = 0;
37 for &c in self.0.borrow().iter() {
38 if c == 8 {
39 i -= 1;
40 } else {
41 if i < result.len() {
42 result[i] = c;
43 } else {
44 result.push(c);
45 }
46 i += 1;
47 }
48 }
49 from_utf8(&result).unwrap().to_string()
50 }
51 fn len(&self) -> u64 {
52 self.0.borrow().len() as u64
53 }
54 fn metadata(&self) -> Metadata {
55 Metadata {
56 len: self.len(),
57 is_dir: false,
58 }
59 }
60}
61
62impl Write for VecRef {
63 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
64 self.0.borrow_mut().extend_from_slice(buf);
65 Ok(buf.len())
66 }
67 fn flush(&mut self) -> io::Result<()> {
68 Ok(())
69 }
70}
71
72#[derive(Debug, Default)]
73enum Entity {
74 #[default]
75 Dir,
76 File(VecRef),
77}
78
79impl Entity {
80 fn metadata(&self) -> Metadata {
81 match self {
82 Entity::Dir => Metadata {
83 len: 0,
84 is_dir: true,
85 },
86 Entity::File(x) => x.metadata(),
87 }
88 }
89}
90
91#[derive(Debug, Default)]
92pub struct FileSystem {
93 entity_map: BTreeMap<String, Entity>,
94}
95
96impl FileSystem {
97 pub fn check_dir(&self, path: &str) -> io::Result<()> {
98 if let Some(Entity::Dir) = self.entity_map.get(path) {
99 Ok(())
100 } else {
101 Err(not_found())
102 }
103 }
104 pub fn check_parent(&self, path: &str) -> io::Result<()> {
105 if let Some(d) = path.rfind('/').map(|i| &path[..i]) {
106 self.check_dir(d)
107 } else {
108 Ok(())
109 }
110 }
111}
112
113pub struct DirEntry {
114 path: String,
115 metadata: Metadata,
116}
117
118impl io_trait::DirEntry for DirEntry {
119 type Metadata = Metadata;
120 fn path(&self) -> String {
121 self.path.clone()
122 }
123 fn metadata(&self) -> io::Result<Self::Metadata> {
124 Ok(self.metadata.clone())
125 }
126}
127
128pub struct VirtualIo {
129 pub args: Vec<String>,
130 pub fs: RefCell<FileSystem>,
131 pub stdout: VecRef,
132 pub duration: RefCell<Duration>,
133}
134
135impl VirtualIo {
136 pub fn new(args: &[&str]) -> Self {
137 Self {
138 args: once("blockset".to_string())
139 .chain(args.iter().map(|v| v.to_string()))
140 .collect(),
141 fs: Default::default(),
142 stdout: Default::default(),
143 duration: Default::default(),
144 }
145 }
146}
147
148#[derive(Debug)]
149pub struct MemFile {
150 vec_ref: VecRef,
151 pos: usize,
152}
153
154impl MemFile {
155 fn new(vec_ref: VecRef) -> Self {
156 Self { vec_ref, pos: 0 }
157 }
158}
159
160impl File for MemFile {
161 type Metadata = Metadata;
162 fn metadata(&self) -> io::Result<Self::Metadata> {
163 Ok(self.vec_ref.metadata())
164 }
165}
166
167impl Seek for MemFile {
168 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
169 self.pos = match pos {
170 io::SeekFrom::Start(x) => x as usize,
171 io::SeekFrom::End(x) => (self.vec_ref.len() as i64 + x) as usize,
172 io::SeekFrom::Current(x) => (self.pos as i64 + x) as usize,
173 };
174 Ok(self.pos as u64)
175 }
176}
177
178impl Read for MemFile {
179 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
180 let source = &self.vec_ref.0.borrow()[self.pos..];
181 let len = source.len().min(buf.len());
182 buf[..len].copy_from_slice(&source[..len]);
183 self.pos += len;
184 Ok(len)
185 }
186}
187
188impl Write for MemFile {
189 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
190 let pos = self.pos;
191 let buf_len = buf.len();
192 let end = pos + buf_len;
193 {
194 let mut v = self.vec_ref.0.borrow_mut();
195 if end > v.len() {
196 v.resize(end, 0);
197 }
198 v[pos..end].copy_from_slice(buf);
199 }
200 self.pos = end;
201 Ok(buf_len)
202 }
203 fn flush(&mut self) -> io::Result<()> {
204 self.vec_ref.flush()
205 }
206}
207
208fn not_found() -> io::Error {
209 io::Error::new(io::ErrorKind::NotFound, "file not found")
210}
211
212fn check_path(a: &str) -> io::Result<()> {
213 if a.chars()
214 .all(|c| c.is_ascii_alphanumeric() || "/_.-".contains(c))
215 {
216 Ok(())
217 } else {
218 Err(io::Error::new(
219 io::ErrorKind::InvalidInput,
220 "invalid file name",
221 ))
222 }
223}
224
225impl Io for VirtualIo {
226 type File = MemFile;
227 type Stdout = VecRef;
228 type Args = vec::IntoIter<String>;
229 type Metadata = Metadata;
230 type DirEntry = DirEntry;
231 type Instant = Duration;
232 fn args(&self) -> Self::Args {
233 self.args.clone().into_iter()
234 }
235 fn metadata(&self, path: &str) -> io::Result<Metadata> {
236 let fs = self.fs.borrow();
237 let dir_end = path.ends_with('/');
238 let path = if dir_end {
239 &path[..path.len() - 1]
240 } else {
241 path
242 };
243 let result = fs
244 .entity_map
245 .get(path)
246 .map(Entity::metadata)
247 .ok_or_else(not_found)?;
248 if !result.is_dir && dir_end {
249 return Err(not_found());
250 }
251 Ok(result)
252 }
253 fn create(&self, path: &str) -> io::Result<Self::File> {
254 let mut fs = self.fs.borrow_mut();
255 fs.check_parent(path)?;
256 let vec_ref = VecRef::default();
257 check_path(path)?;
258 fs.entity_map
259 .insert(path.to_string(), Entity::File(vec_ref.clone()));
260 Ok(MemFile::new(vec_ref))
261 }
262 fn create_dir(&self, path: &str) -> io::Result<()> {
263 let mut fs = self.fs.borrow_mut();
264 fs.entity_map.insert(path.to_string(), Entity::Dir);
265 Ok(())
266 }
267 fn open(&self, path: &str) -> io::Result<Self::File> {
268 let fs = self.fs.borrow();
269 fs.check_parent(path)?;
270 check_path(path)?;
271 fs.entity_map
272 .get(path)
273 .and_then(|v| {
274 if let Entity::File(x) = v {
275 Some(MemFile::new(x.to_owned()))
276 } else {
277 None
278 }
279 })
280 .ok_or_else(not_found)
281 }
282 fn stdout(&self) -> VecRef {
283 self.stdout.clone()
284 }
285
286 fn read_dir(&self, path: &str) -> io::Result<Vec<DirEntry>> {
287 let fs = self.fs.borrow();
288 fs.check_dir(path)?;
289 let i = fs.entity_map.iter().map(|(p, e)| DirEntry {
290 path: p.to_owned(),
291 metadata: e.metadata(),
292 });
293 let x = i
294 .filter(|p| {
295 if let Some((a, _)) = p.path.rsplit_once('/') {
296 a == path
297 } else {
298 false
299 }
300 })
301 .collect();
302 Ok(x)
303 }
304
305 fn now(&self) -> Self::Instant {
306 let mut d = self.duration.borrow_mut();
307 let result = *d;
308 *d = d.add(Duration::from_millis(1));
309 result
310 }
311
312 fn current_dir(&self) -> io::Result<String> {
313 Ok(String::default())
314 }
315 fn set_current_dir(&self, path: &str) -> io::Result<()> {
316 if path.is_empty() {
317 Ok(())
318 } else {
319 Err(io::Error::new(
320 io::ErrorKind::NotFound,
321 "directory not found",
322 ))
323 }
324 }
325}
326
327#[cfg(test)]
328mod test {
329 use std::io::{self, Seek, SeekFrom, Write};
330
331 use io_trait::{DirEntry, File, Io, Metadata};
332 use wasm_bindgen_test::wasm_bindgen_test;
333
334 use crate::VirtualIo;
335
336 #[wasm_bindgen_test]
337 #[test]
338 fn test() {
339 fn check_len(m: &super::Metadata, f: fn(m: &super::Metadata) -> u64, len: u64) {
340 assert_eq!(f(m), len);
341 }
342 fn check_current_dir(
343 io: &VirtualIo,
344 path: &str,
345 f: fn(x: &VirtualIo) -> io::Result<String>,
346 ) {
347 assert_eq!(f(io).unwrap(), path);
348 }
349 let io = VirtualIo::new(&[]);
350 io.write("test.txt", "Hello, world!".as_bytes()).unwrap();
351 let result = io.read_to_string("test.txt").unwrap();
352 assert_eq!(result, "Hello, world!");
353 check_len(&io.metadata("test.txt").unwrap(), Metadata::len, 13);
354 check_current_dir(&io, "", VirtualIo::current_dir);
356 }
358
359 #[wasm_bindgen_test]
360 #[test]
361 fn test_args() {
362 let mut io = VirtualIo::new(&[]);
363 io.args = ["a".to_string(), "b".to_string()].to_vec();
364 let x = io.args().collect::<Vec<_>>();
365 assert_eq!(&x, &["a", "b"]);
366 }
367
368 #[wasm_bindgen_test]
369 #[test]
370 fn test_stdout() {
371 {
372 let io = VirtualIo::new(&[]);
373 let mut s = io.stdout();
374 s.write(b"Hello, world!\x08?").unwrap();
375 assert_eq!(s.to_stdout(), "Hello, world?");
376 }
377 {
378 let io = VirtualIo::new(&[]);
379 let mut s = io.stdout();
380 s.write(b"Hello, world!\x08\x08?").unwrap();
381 assert_eq!(s.to_stdout(), "Hello, worl?!");
382 }
383 }
384
385 #[wasm_bindgen_test]
386 #[test]
387 fn test_write() {
388 let io = VirtualIo::new(&[]);
389 io.write("test.txt", "Hello, world!".as_bytes()).unwrap();
390 let result = io.read("test.txt").unwrap();
391 assert_eq!(result, "Hello, world!".as_bytes());
392 }
393
394 #[wasm_bindgen_test]
395 #[test]
396 fn test_write_file() {
397 fn flush<W: Write>(w: &mut W, f: fn(&mut W) -> io::Result<()>) {
398 f(w).unwrap();
399 }
400 let i = VirtualIo::new(&[]);
401 {
402 let mut f = i.create("test.txt").unwrap();
403 f.write("Hello, world!".as_bytes()).unwrap();
404 f.write("?".as_bytes()).unwrap();
405 flush(&mut f, Write::flush);
406 let m = f.metadata().unwrap();
407 assert_eq!(m.len(), 14);
408 assert!(!m.is_dir());
409 }
410 let result = i.read("test.txt").unwrap();
411 assert_eq!(result, "Hello, world!?".as_bytes());
412 }
413
414 #[wasm_bindgen_test]
415 #[test]
416 fn test_write_seek() {
417 let io = VirtualIo::new(&[]);
418 {
419 let mut f = io.create("test.txt").unwrap();
420 f.write("Hello, world!".as_bytes()).unwrap();
421 f.seek(SeekFrom::Start(7)).unwrap();
422 f.write("there!".as_bytes()).unwrap();
423 f.flush().unwrap();
424 let m = f.metadata().unwrap();
425 assert_eq!(m.len(), 13);
426 assert!(!m.is_dir());
427 }
428 let result = io.read("test.txt").unwrap();
429 assert_eq!(result, "Hello, there!".as_bytes());
430 }
431
432 #[wasm_bindgen_test]
433 #[test]
434 fn test_write_seek_current() {
435 let io = VirtualIo::new(&[]);
436 {
437 let mut f = io.create("test.txt").unwrap();
438 f.write("Hello, world!".as_bytes()).unwrap();
439 f.seek(SeekFrom::Current(2)).unwrap();
440 f.write("there".as_bytes()).unwrap();
441 f.flush().unwrap();
442 let m = f.metadata().unwrap();
443 assert_eq!(m.len(), 20);
444 assert!(!m.is_dir());
445 }
446 let result = io.read("test.txt").unwrap();
447 assert_eq!(result, "Hello, world!\0\0there".as_bytes());
448 }
449
450 #[wasm_bindgen_test]
451 #[test]
452 fn test_write_seek_end() {
453 let io = VirtualIo::new(&[]);
454 {
455 let mut f = io.create("test.txt").unwrap();
456 f.write("Hello, world!".as_bytes()).unwrap();
457 f.seek(SeekFrom::End(-2)).unwrap();
458 f.write("there".as_bytes()).unwrap();
459 f.flush().unwrap();
460 let m = f.metadata().unwrap();
461 assert_eq!(m.len(), 16);
462 assert!(!m.is_dir());
463 }
464 let result = io.read("test.txt").unwrap();
465 assert_eq!(result, "Hello, worlthere".as_bytes());
466 }
467
468 #[wasm_bindgen_test]
469 #[test]
470 fn test_dir_fail() {
471 let io = VirtualIo::new(&[]);
472 assert!(io.write("a/test.txt", "Hello, world!".as_bytes()).is_err());
473 assert!(io.open("a").is_err());
474 }
475
476 #[wasm_bindgen_test]
477 #[test]
478 fn test_write_recursively() {
479 let io = VirtualIo::new(&[]);
480 assert!(io
481 .write_recursively("a/test.txt", "Hello, world!".as_bytes())
482 .is_ok());
483 assert!(io
484 .write_recursively("a/test2.txt", "Hello, world!".as_bytes())
485 .is_ok());
486 assert!(io.open("a").is_err());
487 }
488
489 #[wasm_bindgen_test]
490 #[test]
491 fn test_dir_rec() {
492 let io = VirtualIo::new(&[]);
493 assert!(io
494 .write_recursively("a/b/test.txt", "Hello, world!".as_bytes())
495 .is_ok());
496 let x = io
497 .read_dir_type("a", true)
498 .unwrap()
499 .iter()
500 .map(|v| v.path().to_owned())
501 .collect::<Vec<_>>();
502 assert_eq!(x, ["a/b"].to_vec());
503 }
504
505 #[wasm_bindgen_test]
506 #[test]
507 fn test_err() {
508 let io = VirtualIo::new(&[]);
509 assert!(io
510 .write_recursively("?", "Hello, world!".as_bytes())
511 .is_err());
512 }
513
514 #[wasm_bindgen_test]
515 #[test]
516 fn test_now() {
517 let io = VirtualIo::new(&[]);
518 assert_eq!(io.now().as_millis(), 0);
519 assert_eq!(io.now().as_millis(), 1);
520 }
521
522 fn check_len(m: &super::Metadata, f: fn(m: &super::Metadata) -> u64, len: u64) {
523 assert_eq!(m.len(), len);
524 }
525
526 #[wasm_bindgen_test]
527 #[test]
528 fn test_metadata() {
529 let io = VirtualIo::new(&[]);
530 io.write("test.txt", "Hello, world!".as_bytes()).unwrap();
531 io.create_dir("a").unwrap();
532 {
533 let m = io.metadata("test.txt").unwrap();
534 check_len(&m, super::Metadata::len, 13);
536 assert!(!m.is_dir());
537 }
538 {
539 io.metadata("test.txt/").unwrap_err();
540 }
541 {
542 io.metadata("b").unwrap_err();
543 }
544 {
545 let m = io.metadata("a").unwrap();
546 assert!(m.is_dir());
547 }
548 {
549 let m = io.metadata("a/").unwrap();
550 assert!(m.is_dir());
551 }
552 }
553
554 #[wasm_bindgen_test]
555 #[test]
556 fn test_set_current_dir() {
557 let io = VirtualIo::new(&[]);
558 assert!(io.set_current_dir("").is_ok());
559 assert!(io.set_current_dir("a").is_err());
560 }
561}