1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use crate::{
  io::IO,
  ui::UILock,
  buffer::iters::LinesIter,
};
use super::Result;

#[derive(Debug, PartialEq)]
pub enum FakeIOError {
  ChildExitError,
  NotFound,
}
impl std::fmt::Display for FakeIOError {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    use FakeIOError::*;
    match self {
      ChildExitError => write!(f,"Child process returned error after running."),
      NotFound => write!(f,"Could not open file. Not found or invalid path."),
    }
  }
}
impl std::error::Error for FakeIOError {}
impl crate::error::IOErrorTrait for FakeIOError {}

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash, Clone)]
pub struct ShellCommand {
  pub command: String,
  pub input: String,
}

/// An [`IO`] implementation intended to simulate filesystem and shell
/// interactions for testing.
#[derive(Clone)]
pub struct FakeIO {
  pub fake_fs: HashMap<String, String>,
  pub fake_shell: HashMap<ShellCommand, String>,
}

impl IO for FakeIO {
  /// Returns [`FakeIOError::ChildExitError`] if command is not represented by a
  /// [`ShellCommand`] with empty input in `fake_shell`.
  fn run_command(&mut self,
    _ui: &mut UILock,
    command: String,
  ) -> Result<()> {
    if self.fake_shell.contains_key(
      &ShellCommand{command, input: String::new()}
    ) {
      Ok(())
    } else {
      // sh is child and returns error on command not found
      Err(FakeIOError::ChildExitError.into())
    }
  }
  /// Returns [`FakeIOError::ChildExitError`] if command is not represented by a
  /// [`ShellCommand`] with empty input in `fake_shell`.
  fn run_read_command(&mut self,
    _ui: &mut UILock,
    command: String,
  ) -> Result<String> {
    match self.fake_shell.get(
      &ShellCommand{command, input: String::new()}
    ) {
      Some(x) => Ok(x.to_owned()),
      // sh is child and returns error on command not found
      None => Err(FakeIOError::ChildExitError.into()),
    }
  }
  /// Returns [`FakeIOError::ChildExitError`] if command is not represented by a
  /// [`ShellCommand`] with the given input in `fake_shell`.
  fn run_write_command(&mut self,
    _ui: &mut UILock,
    command: String,
    input: LinesIter,
  ) -> Result<usize> {
    let input = input.fold(String::new(), |mut s, x| {s.push_str(x); s});
    let inputlen = input.len();
    match self.fake_shell.get(
      &ShellCommand{command, input}
    ) {
      Some(_) => Ok(inputlen),
      // sh is child and returns error on command not found
      None => Err(FakeIOError::ChildExitError.into()),
    }
  }
  /// Returns [`FakeIOError::ChildExitError`] if command is not represented by a
  /// [`ShellCommand`] with the given input in `fake_shell`.
  fn run_transform_command(&mut self,
    _ui: &mut UILock,
    command: String,
    input: LinesIter,
  ) -> Result<String> {
    let input = input.fold(String::new(), |mut s, x| {s.push_str(x); s});
    match self.fake_shell.get(
      &ShellCommand{command, input}
    ) {
      Some(x) => Ok(x.to_owned()),
      // sh is child and returns error on command not found
      None => Err(FakeIOError::ChildExitError.into()),
    }
  }
  fn write_file(&mut self,
    path: &str,
    append: bool,
    data: LinesIter,
  ) -> Result<usize> {
    let base_data = if append {
      match self.fake_fs.get(path) {
        Some(x) => x.clone(),
        None => String::new(),
      }
    } else {
      String::new()
    };
    let data = data.fold(base_data, |mut s, x|{s.push_str(x); s});
    let datalen = data.len();
    self.fake_fs.insert(path.to_owned(), data);
    Ok(datalen)
  }
  /// Returns [`FakeIOError::NotFound`] if `fake_fs` doesn't have an entry with
  /// the given path as key.
  fn read_file(&mut self,
    path: &str,
    must_exist: bool,
  ) -> Result<String> {
    match self.fake_fs.get(path) {
      Some(x) => Ok(x.to_owned()),
      None => if must_exist {
        Err(FakeIOError::NotFound.into())
      } else {
        Ok(String::new())
      },
    }
  }
}