├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── README.md
├── src
├── bin
│ └── repeat.rs
├── command.rs
├── lib.rs
├── local_shell.rs
├── process_manager.rs
├── result.rs
├── shell_child.rs
└── shell_command.rs
└── tests
└── shell_tests.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.rs.bk
2 | *.swp
3 | /target/
4 | Cargo.lock
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "shell"
3 | version = "0.1.0"
4 | authors = ["Daichi Hirono "]
5 |
6 | [dependencies]
7 | errno = "*"
8 | lazy_static = "1.0"
9 | libc = "0.2"
10 | log = "0.3.8"
11 | regex = "0.2"
12 | env_logger = "0.4.3"
13 | nom = "3.2"
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rust shell - shell script written in rust.
2 |
3 | This is not an officially supported Google product
4 |
5 | Rust shell is a helper library for std::process::Command to write shell
6 | script like tasks in rust. The library only works with unix-like operation
7 | systems.
8 |
9 | ## Run command
10 |
11 | `run!` macro creates a ShellCommand instance which you can run by `run()`
12 | method.
13 |
14 | ```
15 | #[macro_use] extern crate shell;
16 |
17 | // Run command by cmd! macro
18 | cmd!("echo Hello rust shell!").run().unwrap();
19 |
20 | // Contain white space or non-alphabetical characters
21 | cmd!("echo \"%$#\"").run().unwrap();
22 |
23 | // Pass an argument
24 | let name = "shell";
25 | cmd!("echo Hello rust {}!", name).run().unwrap();
26 |
27 | // Extract environment variable
28 | cmd!("echo HOME is $HOME").run().unwrap();
29 | ```
30 | ## ShellResult
31 |
32 | The return value of `ShellCommand#run()` is `ShellResult` which is `Ok(_)`
33 | only when the command successfully runs and its execution code is 0, so you
34 | can use `?` operator to check if the command successfully exits or not.
35 |
36 | ```
37 | #[macro_use] extern crate shell;
38 | use shell::ShellResult;
39 |
40 | fn shell_function() -> ShellResult {
41 | cmd!("echo Command A").run()?;
42 | cmd!("echo Command B").run()?;
43 | shell::ok()
44 | }
45 | ```
46 |
47 | ## Output string
48 |
49 | ShellCommand has a shorthand to obtain stdout as UTF8 string.
50 |
51 | ```
52 | #[macro_use] extern crate shell;
53 |
54 | assert_eq!(cmd!("echo OK").stdout_utf8().unwrap(), "OK\n");
55 | ```
56 |
57 | ## Spawn
58 |
59 | ShellCommand has `spawn()` method which runs the command asynchronously and
60 | returns `ShellChild`.
61 |
62 | ```
63 | #[macro_use] extern crate shell;
64 | extern crate libc;
65 | use shell::ShellResultExt;
66 |
67 | // Wait
68 | let child = cmd!("sleep 2").spawn().unwrap();
69 | child.wait().unwrap();
70 |
71 | // Signal
72 | let child = cmd!("sleep 2").spawn().unwrap();
73 | child.signal(libc::SIGINT);
74 | let result = child.wait();
75 | assert!(result.is_err(), "Should be error as it exits with a signal");
76 | assert!(result.status().is_ok(), "Still able to obtain status");
77 | ```
78 |
79 | ## Thread
80 |
81 | If you would like to run a sequence of commands asynchronously,
82 | `shell::spawn` creates a thread as well as `std::thread::spawn` but it
83 | returns `ShellHandle` wrapping `std::thread::JoinHandle`.
84 |
85 | `ShellHandle#signal()` is used to send a signal to processes running on the
86 | thread. It also stops launching a new process by `ShellComamnd::run()` on
87 | that thread.
88 |
89 | ```
90 | #[macro_use] extern crate shell;
91 | extern crate libc;
92 | use shell::ShellResult;
93 | use shell::ShellResultExt;
94 |
95 | let handle = shell::spawn(|| -> ShellResult {
96 | cmd!("sleep 3").run()
97 | });
98 | handle.signal(libc::SIGINT);
99 | let result = handle.join().unwrap();
100 | assert!(result.is_err(), "Should be error as it exits with a signal");
101 | assert!(result.status().is_ok(), "Still able to obtain status");
102 | ```
103 |
104 | ## Signal handling
105 |
106 | `trap_signal_and_wait_children()` starts watching SIGINT and SIGTERM, and
107 | waits all child processes before exiting the process when receiving these
108 | signals. The function needs to be called before launching any new thread.
109 |
110 | ```
111 | extern crate shell;
112 | shell::trap_signal_and_wait_children().unwrap();
113 | ```
114 |
115 | ## Access underlaying objects
116 |
117 | `ShellComamnd` wraps `std::process::Command` and `ShellChild` wraps
118 | `std::process::Child`. Both underlaying objects are accessible via public
119 | fields.
120 |
121 | ```
122 | #[macro_use] extern crate shell;
123 | use std::process::Stdio;
124 | use std::io::Read;
125 |
126 | // Access std::process::Command.
127 | let mut shell_command = cmd!("echo OK");
128 | {
129 | let mut command = &mut shell_command.command;
130 | command.stdout(Stdio::piped());
131 | }
132 |
133 | // Access std::process::Child.
134 | let shell_child = shell_command.spawn().unwrap();
135 | {
136 | let mut lock = shell_child.0.write().unwrap();
137 | let mut child = &mut lock.as_mut().unwrap().child;
138 | let mut str = String::new();
139 | child.stdout.as_mut().unwrap().read_to_string(&mut str);
140 | }
141 | shell_child.wait().unwrap();
142 | ```
143 |
144 | ## License
145 | Apatch 2 License
146 |
--------------------------------------------------------------------------------
/src/bin/repeat.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! This is a recurring task of developping rust shell. The binary generates
16 | //! README.md from lib.rs's module comments, runs test with preferred option,
17 | //! generates docs, then watches file changes by using inotifywait command.
18 |
19 | #[macro_use] extern crate shell;
20 |
21 | use shell::ShellResult;
22 | use shell::ShellResultExt;
23 | use std::env;
24 | use std::fs::File;
25 | use std::io;
26 | use std::io::BufRead;
27 | use std::io::BufReader;
28 | use std::io::Write;
29 | use std::os::unix::process::CommandExt;
30 |
31 | fn create_readme() -> io::Result {
32 | let file = File::open("src/lib.rs")?;
33 | let file = BufReader::new(file);
34 |
35 | let mut codeblock = false;
36 | let mut readme = String::new();
37 | for line in file.lines() {
38 | if line.is_err() {
39 | break;
40 | }
41 | let line = line.unwrap();
42 | if !line.starts_with("//!") {
43 | continue;
44 | }
45 | let line = if line.len() > 3 { &line[4..] } else { "" };
46 | if codeblock && line.starts_with("# ") {
47 | continue;
48 | }
49 | if line.starts_with("```") {
50 | codeblock = !codeblock;
51 | }
52 | readme.push_str(line);
53 | readme.push('\n');
54 | }
55 | Ok(readme)
56 | }
57 |
58 | fn write_readme(readme: &str) -> io::Result<()> {
59 | let mut file = File::create("README.md")?;
60 | file.write_all(readme.as_bytes())
61 | }
62 |
63 | fn step() -> ShellResult {
64 | write_readme(&create_readme()?)?;
65 | cmd!("cargo test -- --test-threads=1").run()?;
66 | cmd!("cargo doc").run()
67 | }
68 |
69 | fn main() {
70 | env::set_var("RUST_LOG", "shell=debug");
71 | step().status().unwrap();
72 |
73 | loop {
74 | cmd!("inotifywait -e close_write -r src").run().unwrap();
75 | if cmd!("cargo build").run().status().unwrap().success() {
76 | break;
77 | }
78 | }
79 |
80 | println!("Reload repeat command");
81 | cmd!("cargo run --bin repeat").command.exec();
82 | }
83 |
--------------------------------------------------------------------------------
/src/command.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use shell_command::ShellCommand;
16 | use nom::IResult;
17 | use std::process::Command;
18 | use std::env;
19 | use std::env::VarError;
20 |
21 | fn token_char(ch: char) -> bool {
22 | if ch.len_utf8() > 1 {
23 | return false;
24 | }
25 | match ch {
26 | '\x00' ... '\x20' => false,
27 | '\x7f' | '"' | '\'' | '>' | '<' | '|' | ';' | '{' | '}' | '$' => false,
28 | _ => true,
29 | }
30 | }
31 |
32 | fn var_char(ch: char) -> bool {
33 | match ch {
34 | 'a' ... 'z' => true,
35 | 'A' ... 'Z' => true,
36 | '0' ... '9' => true,
37 | '_' => true,
38 | _ => false,
39 | }
40 | }
41 |
42 | enum TokenPart {
43 | Bare(String),
44 | Placeholder,
45 | EnvVariable(String),
46 | }
47 |
48 | struct Token(Vec);
49 |
50 | impl Token {
51 | fn into_string(self, args: &mut Iterator)
52 | -> Result {
53 | let mut token = String::from("");
54 | for part in self.0 {
55 | match part {
56 | TokenPart::Bare(s) => token += &s,
57 | TokenPart::Placeholder =>
58 | token += args.next().expect("Too many placeholders"),
59 | TokenPart::EnvVariable(name) => {
60 | debug!("Environment variable {}", name);
61 | token += &env::var(name)?
62 | }
63 | }
64 | }
65 | Ok(token)
66 | }
67 | }
68 |
69 | named!(bare_token<&str, TokenPart>,
70 | map!(take_while1_s!(token_char), |s| TokenPart::Bare(String::from(s))));
71 | named!(quoted_token<&str, TokenPart>,
72 | map!(delimited!(tag_s!("\""), take_until_s!("\""), tag_s!("\"")),
73 | |s| TokenPart::Bare(String::from(s))));
74 | named!(place_holder<&str, TokenPart>,
75 | map!(tag_s!("{}"), |_| TokenPart::Placeholder));
76 | named!(env_var<&str, TokenPart>,
77 | map!(preceded!(tag!("$"), take_while1_s!(var_char)),
78 | |name| TokenPart::EnvVariable(String::from(name))));
79 | named!(command_token<&str, Token>,
80 | map!(many1!(alt!(bare_token | quoted_token | place_holder | env_var)),
81 | |vec| Token(vec)));
82 |
83 | named!(command< &str, Vec >,
84 | terminated!(ws!(many1!(command_token)), eof!()));
85 |
86 | /// Creates a new commadn from `format` and `args`
87 | /// # Examples
88 | #[macro_export]
89 | macro_rules! cmd {
90 | ($format:expr) => ($crate::new_command($format, &[]).unwrap());
91 | ($format:expr, $($arg:expr),+) =>
92 | ($crate::new_command($format, &[$($arg),+]).unwrap());
93 | }
94 |
95 | fn parse_cmd<'a>(format: &'a str, args: &'a [&str])
96 | -> Result, VarError> {
97 | let tokens = match command(format) {
98 | IResult::Done(_, result) => result,
99 | IResult::Error(error) => panic!("Error {:?}", error),
100 | IResult::Incomplete(needed) => panic!("Needed {:?}", needed)
101 | };
102 | let args = args.iter().map(|a| *a).collect::>();
103 | let mut args = args.into_iter();
104 | tokens.into_iter().map(|token| token.into_string(&mut args))
105 | .collect::, _>>()
106 | }
107 |
108 | /// Creates a new command from `format` and `args`.
109 | /// The function is invoked from `cmd!` macro internally.
110 | pub fn new_command(format: &str, args: &[&str])
111 | -> Result {
112 | let vec = parse_cmd(format, args)?;
113 | let mut command = Command::new(&vec[0]);
114 | if vec.len() > 1 {
115 | command.args(&vec[1..]);
116 | }
117 | let line = vec.join(" ");
118 | Ok(ShellCommand::new(line, command))
119 | }
120 |
121 |
122 | #[test]
123 | fn test_parse_cmd() {
124 | let tokens = parse_cmd(r#"cmd 1 2
125 | 3 "
126 | 4" {}"#, &["5"]).unwrap();
127 | assert_eq!("cmd", tokens[0]);
128 | assert_eq!("1", tokens[1]);
129 | assert_eq!("2", tokens[2]);
130 | assert_eq!("3", tokens[3]);
131 | assert_eq!("\n 4", tokens[4]);
132 | assert_eq!("5", tokens[5]);
133 | }
134 |
135 | #[test]
136 | fn test_parse_cmd_env() {
137 | use env_logger;
138 | env_logger::init().unwrap();
139 | env::set_var("MY_VAR", "VALUE");
140 | let tokens = parse_cmd("echo $MY_VAR/dir", &[]).unwrap();
141 | assert_eq!("VALUE/dir", tokens[1]);
142 | }
143 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! # Rust shell - shell script written in rust.
16 | //!
17 | //! This is not an officially supported Google product
18 | //!
19 | //! Rust shell is a helper library for std::process::Command to write shell
20 | //! script like tasks in rust. The library only works with unix-like operation
21 | //! systems.
22 | //!
23 | //! ## Run command
24 | //!
25 | //! `run!` macro creates a ShellCommand instance which you can run by `run()`
26 | //! method.
27 | //!
28 | //! ```
29 | //! #[macro_use] extern crate shell;
30 | //!
31 | //! # fn main() {
32 | //! // Run command by cmd! macro
33 | //! cmd!("echo Hello rust shell!").run().unwrap();
34 | //!
35 | //! // Contain white space or non-alphabetical characters
36 | //! cmd!("echo \"%$#\"").run().unwrap();
37 | //!
38 | //! // Pass an argument
39 | //! let name = "shell";
40 | //! cmd!("echo Hello rust {}!", name).run().unwrap();
41 | //!
42 | //! // Extract environment variable
43 | //! cmd!("echo HOME is $HOME").run().unwrap();
44 | //! # }
45 | //! ```
46 | //! ## ShellResult
47 | //!
48 | //! The return value of `ShellCommand#run()` is `ShellResult` which is `Ok(_)`
49 | //! only when the command successfully runs and its execution code is 0, so you
50 | //! can use `?` operator to check if the command successfully exits or not.
51 | //!
52 | //! ```
53 | //! #[macro_use] extern crate shell;
54 | //! use shell::ShellResult;
55 | //!
56 | //! # fn main() {
57 | //! # shell_function().unwrap();
58 | //! # }
59 | //! fn shell_function() -> ShellResult {
60 | //! cmd!("echo Command A").run()?;
61 | //! cmd!("echo Command B").run()?;
62 | //! shell::ok()
63 | //! }
64 | //! ```
65 | //!
66 | //! ## Output string
67 | //!
68 | //! ShellCommand has a shorthand to obtain stdout as UTF8 string.
69 | //!
70 | //! ```
71 | //! #[macro_use] extern crate shell;
72 | //!
73 | //! # fn main() {
74 | //! assert_eq!(cmd!("echo OK").stdout_utf8().unwrap(), "OK\n");
75 | //! # }
76 | //! ```
77 | //!
78 | //! ## Spawn
79 | //!
80 | //! ShellCommand has `spawn()` method which runs the command asynchronously and
81 | //! returns `ShellChild`.
82 | //!
83 | //! ```
84 | //! #[macro_use] extern crate shell;
85 | //! extern crate libc;
86 | //! use shell::ShellResultExt;
87 | //!
88 | //! # fn main() {
89 | //! // Wait
90 | //! let child = cmd!("sleep 2").spawn().unwrap();
91 | //! child.wait().unwrap();
92 | //!
93 | //! // Signal
94 | //! let child = cmd!("sleep 2").spawn().unwrap();
95 | //! child.signal(libc::SIGINT);
96 | //! let result = child.wait();
97 | //! assert!(result.is_err(), "Should be error as it exits with a signal");
98 | //! assert!(result.status().is_ok(), "Still able to obtain status");
99 | //! # }
100 | //! ```
101 | //!
102 | //! ## Thread
103 | //!
104 | //! If you would like to run a sequence of commands asynchronously,
105 | //! `shell::spawn` creates a thread as well as `std::thread::spawn` but it
106 | //! returns `ShellHandle` wrapping `std::thread::JoinHandle`.
107 | //!
108 | //! `ShellHandle#signal()` is used to send a signal to processes running on the
109 | //! thread. It also stops launching a new process by `ShellComamnd::run()` on
110 | //! that thread.
111 | //!
112 | //! ```
113 | //! #[macro_use] extern crate shell;
114 | //! extern crate libc;
115 | //! use shell::ShellResult;
116 | //! use shell::ShellResultExt;
117 | //!
118 | //! # fn main() {
119 | //! let handle = shell::spawn(|| -> ShellResult {
120 | //! cmd!("sleep 3").run()
121 | //! });
122 | //! handle.signal(libc::SIGINT);
123 | //! let result = handle.join().unwrap();
124 | //! assert!(result.is_err(), "Should be error as it exits with a signal");
125 | //! assert!(result.status().is_ok(), "Still able to obtain status");
126 | //! # }
127 | //! ```
128 | //!
129 | //! ## Signal handling
130 | //!
131 | //! `trap_signal_and_wait_children()` starts watching SIGINT and SIGTERM, and
132 | //! waits all child processes before exiting the process when receiving these
133 | //! signals. The function needs to be called before launching any new thread.
134 | //!
135 | //! ```
136 | //! extern crate shell;
137 | //! shell::trap_signal_and_wait_children().unwrap();
138 | //! ```
139 | //!
140 | //! ## Access underlaying objects
141 | //!
142 | //! `ShellComamnd` wraps `std::process::Command` and `ShellChild` wraps
143 | //! `std::process::Child`. Both underlaying objects are accessible via public
144 | //! fields.
145 | //!
146 | //! ```
147 | //! #[macro_use] extern crate shell;
148 | //! use std::process::Stdio;
149 | //! use std::io::Read;
150 | //!
151 | //! # fn main() {
152 | //! // Access std::process::Command.
153 | //! let mut shell_command = cmd!("echo OK");
154 | //! {
155 | //! let mut command = &mut shell_command.command;
156 | //! command.stdout(Stdio::piped());
157 | //! }
158 | //!
159 | //! // Access std::process::Child.
160 | //! let shell_child = shell_command.spawn().unwrap();
161 | //! {
162 | //! let mut lock = shell_child.0.write().unwrap();
163 | //! let mut child = &mut lock.as_mut().unwrap().child;
164 | //! let mut str = String::new();
165 | //! child.stdout.as_mut().unwrap().read_to_string(&mut str);
166 | //! }
167 | //! shell_child.wait().unwrap();
168 | //! # }
169 | //! ```
170 | //!
171 | //! ## License
172 | //! Apatch 2 License
173 |
174 | #[macro_use] extern crate lazy_static;
175 | #[macro_use] extern crate log;
176 | #[macro_use] extern crate nom;
177 | extern crate errno;
178 | extern crate libc;
179 | extern crate regex;
180 | extern crate env_logger;
181 |
182 | #[macro_use] mod command;
183 | mod shell_child;
184 | mod shell_command;
185 | mod process_manager;
186 | mod local_shell;
187 | mod result;
188 |
189 | pub use command::new_command;
190 | pub use local_shell::ShellHandle;
191 | pub use local_shell::spawn;
192 | pub use process_manager::trap_signal_and_wait_children;
193 | pub use result::ShellError;
194 | pub use result::ShellResult;
195 | pub use result::ShellResultExt;
196 | pub use result::ok;
197 | pub use shell_child::ShellChild;
198 | pub use shell_child::ShellChildArc;
199 | pub use shell_child::ShellChildCore;
200 | pub use shell_command::ShellCommand;
201 |
--------------------------------------------------------------------------------
/src/local_shell.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use shell_child::ShellChildArc;
16 | use libc::c_int;
17 | use process_manager::PROCESS_MANAGER;
18 | use std::any::Any;
19 | use std::cell::RefCell;
20 | use std::ops::Deref;
21 | use std::sync::Arc;
22 | use std::sync::Mutex;
23 | use std::thread::JoinHandle;
24 | use std::thread::ThreadId;
25 | use std::thread;
26 |
27 | /// Thread local shell.
28 | pub struct LocalShell {
29 | processes: Vec,
30 | signaled: bool
31 | }
32 |
33 | impl LocalShell {
34 | fn new() -> LocalShell {
35 | LocalShell {
36 | processes: Vec::new(),
37 | signaled: false
38 | }
39 | }
40 |
41 | pub fn add_process(&mut self, process: &ShellChildArc) {
42 | self.processes.push(process.clone());
43 | }
44 |
45 | pub fn remove_process(&mut self, process: &ShellChildArc) {
46 | self.processes.retain(|p| !Arc::ptr_eq(p, process));
47 | }
48 |
49 | pub fn signal(&mut self, signal: c_int) {
50 | self.signaled = true;
51 | for process in &self.processes {
52 | let lock = process.read().unwrap();
53 | if let Some(child) = lock.as_ref() {
54 | if let Err(error) = child.signal(signal) {
55 | error!("Failed to send a signal {:?}", error);
56 | }
57 | }
58 | }
59 | }
60 |
61 | pub fn wait(&mut self) {
62 | for process in &self.processes {
63 | let mut lock = process.write().unwrap();
64 | if let Some(child) = lock.take() {
65 | if let Err(error) = child.wait() {
66 | error!("Failed to wait process {:?}", error);
67 | }
68 | }
69 | }
70 | }
71 |
72 | pub fn signaled(&self) -> bool {
73 | self.signaled
74 | }
75 | }
76 |
77 | struct LocalShellScope(ThreadId, Arc>);
78 |
79 | impl LocalShellScope {
80 | fn new(arc: &Arc>) -> LocalShellScope {
81 | let mut lock = PROCESS_MANAGER.lock().unwrap();
82 | let id = thread::current().id();
83 | lock.add_local_shell(&id, arc);
84 |
85 | LocalShellScope(id, arc.clone())
86 | }
87 | }
88 |
89 | impl Default for LocalShellScope {
90 | fn default() -> LocalShellScope {
91 | LocalShellScope::new(&Arc::new(Mutex::new(LocalShell::new())))
92 | }
93 | }
94 |
95 | impl Drop for LocalShellScope {
96 | fn drop(&mut self) {
97 | let mut lock = PROCESS_MANAGER.lock().unwrap();
98 | lock.remove_local_shell(&self.0);
99 | }
100 | }
101 |
102 | pub struct ShellHandle {
103 | join_handle: JoinHandle,
104 | shell: Arc>
105 | }
106 |
107 | impl ShellHandle {
108 | pub fn signal(&self, signal: c_int) {
109 | let mut lock = self.shell.lock().unwrap();
110 | lock.signal(signal);
111 | }
112 |
113 | pub fn join(self) -> Result> {
114 | self.join_handle.join()
115 | }
116 | }
117 |
118 | impl Deref for ShellHandle {
119 | type Target = JoinHandle;
120 | fn deref(&self) -> &Self::Target {
121 | &self.join_handle
122 | }
123 | }
124 |
125 | pub fn spawn(f: F) -> ShellHandle where
126 | F: FnOnce() -> T, F: Send + 'static, T: Send + 'static {
127 | let arc = Arc::new(Mutex::new(LocalShell::new()));
128 | let arc_clone = arc.clone();
129 | let join_handle = thread::spawn(move || -> T {
130 | LOCAL_SHELL_SCOPE.with(|shell| {
131 | let mut shell = shell.borrow_mut();
132 | if shell.is_some() {
133 | panic!("Shell has already registered");
134 | }
135 | *shell = Some(LocalShellScope::new(&arc_clone));
136 | });
137 | f()
138 | });
139 | ShellHandle {
140 | join_handle: join_handle,
141 | shell: arc
142 | }
143 | }
144 |
145 | pub fn current_shell() -> Arc> {
146 | LOCAL_SHELL_SCOPE.with(|shell| {
147 | shell.borrow_mut()
148 | .get_or_insert(LocalShellScope::default()).1.clone()
149 | })
150 | }
151 |
152 | thread_local! {
153 | static LOCAL_SHELL_SCOPE: RefCell