rrun_ssh/
location.rs

1use std::env::{args_os, current_dir};
2use std::ffi::OsString;
3use std::fmt;
4use super::{ProgramArgs, RemoteLocation};
5
6/// Helper method, used for quoting strings.
7fn extend_quoted(onto: &mut String, arg: &str) {
8    onto.push_str(" \'");
9    for ch in arg.chars() {
10        match ch {
11            '\'' => onto.push_str(r"'\''"),
12            '\\' => onto.push_str(r"'\\'"),
13            _ => onto.push(ch),
14        }
15    }
16    onto.push('\'');
17}
18
19/// Encapsulates a directory in which a program can be executed.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum Location {
22    /// Directory on a remote server
23    Remote(RemoteLocation),
24
25    /// Current working directory
26    Local,
27}
28impl Location {
29    /// Using the given command-line arguments, generate a set of program arguments if they exist.
30    pub fn into_args<I>(self, mut args: I) -> Result<ProgramArgs, ()>
31        where I: Iterator<Item = OsString> + ExactSizeIterator
32    {
33        args.next();
34        match self {
35            Location::Local => {
36                let mut prog_args = ProgramArgs::new(try!(args.next().ok_or(())));
37                prog_args.extend(args);
38                Ok(prog_args)
39            }
40
41            Location::Remote(remote) => {
42                if args.len() == 0 {
43                    return Err(());
44                }
45
46                let mut prog_args = ProgramArgs::new("ssh");
47                prog_args.push("-qt");
48                prog_args.push(remote.host);
49
50                let mut cmd = String::from("cd");
51                extend_quoted(&mut cmd, try!(remote.path.to_str().ok_or(())));
52                cmd.push(';');
53                for arg in args {
54                    extend_quoted(&mut cmd, try!(arg.to_str().ok_or(())));
55                }
56                prog_args.push(cmd);
57
58                Ok(prog_args)
59            }
60        }
61    }
62
63    /// Using the program environment, generate a set of program arguments if they exist.
64    #[inline]
65    pub fn into_env_args(self) -> Result<ProgramArgs, ()> {
66        self.into_args(args_os())
67    }
68}
69impl fmt::Display for Location {
70    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71        match *self {
72            Location::Local => fmt::Display::fmt(&current_dir().unwrap().display(), f),
73            Location::Remote(ref remote) => fmt::Display::fmt(&remote, f),
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::{Location, extend_quoted};
81    use super::super::{ProgramArgs, RemoteLocation};
82
83    #[test]
84    fn quote_noescape() {
85        let mut v = String::from("never");
86        extend_quoted(&mut v, "gonna");
87        extend_quoted(&mut v, "give");
88        extend_quoted(&mut v, "you");
89        extend_quoted(&mut v, "up");
90        assert_eq!(v, r"never 'gonna' 'give' 'you' 'up'")
91    }
92
93    #[test]
94    fn quote_escape_quote() {
95        let mut v = String::from("echo");
96        extend_quoted(&mut v, "so");
97        extend_quoted(&mut v, "here's");
98        extend_quoted(&mut v, "a string");
99        extend_quoted(&mut v, "I'd");
100        extend_quoted(&mut v, "print");
101        assert_eq!(v, r"echo 'so' 'here'\''s' 'a string' 'I'\''d' 'print'")
102    }
103
104    #[test]
105    fn quote_escape_slash() {
106        let mut v = String::from("umm");
107        extend_quoted(&mut v, r"/test/");
108        extend_quoted(&mut v, r"\testing\");
109        extend_quoted(&mut v, r"test\\string");
110        assert_eq!(v, r"umm '/test/' ''\\'testing'\\'' 'test'\\''\\'string'")
111    }
112
113    #[test]
114    fn local_empty() {
115        let args = vec!["rrun"].into_iter().map(Into::into);
116        assert!(Location::Local.into_args(args).is_err());
117    }
118
119    #[test]
120    fn local_hello_world() {
121        let args = vec!["rrun", "echo", "Hello,", "world!"].into_iter().map(Into::into);
122
123        let mut test_args = ProgramArgs::new("echo");
124        test_args.push("Hello,");
125        test_args.push("world!");
126
127        assert_eq!(Location::Local.into_args(args), Ok(test_args));
128    }
129
130    #[test]
131    fn remote_empty() {
132        let remote = RemoteLocation::new("someone@example.com:/home/user").unwrap();
133
134        let args = vec!["rrun"].into_iter().map(Into::into);
135
136        assert!(Location::Remote(remote).into_args(args).is_err());
137    }
138
139    #[test]
140    fn remote_hello_world() {
141        let remote = RemoteLocation::new("someone@example.com:/home/user").unwrap();
142
143        let args = vec!["rrun", "echo", "Hello,", "world!"].into_iter().map(Into::into);
144
145        let mut test_args = ProgramArgs::new("ssh");
146        test_args.push("-qt");
147        test_args.push("someone@example.com");
148        test_args.push("cd '/home/user'; 'echo' 'Hello,' 'world!'");
149
150        assert_eq!(Location::Remote(remote).into_args(args), Ok(test_args));
151    }
152}