2018-06-20 17:19:17 -04:00
|
|
|
// Copyright 2017 Jeremy Wall <jeremy@marzhillstudios.com>
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
//! Contains code for converting a UCG Val into an executable script output target.
|
|
|
|
use std;
|
|
|
|
use std::io::{Cursor, Write};
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
2018-12-06 12:23:52 -06:00
|
|
|
use crate::build::Val;
|
|
|
|
use crate::build::Val::Tuple;
|
|
|
|
use crate::convert;
|
2019-02-19 14:50:55 -06:00
|
|
|
use crate::convert::traits::{ConvertResult, Converter};
|
2018-12-06 12:23:52 -06:00
|
|
|
use crate::error::BuildError;
|
|
|
|
use crate::error::ErrorType;
|
2018-06-20 17:19:17 -04:00
|
|
|
|
|
|
|
pub struct ExecConverter {}
|
|
|
|
|
|
|
|
// let exec = {
|
|
|
|
// env = [],
|
|
|
|
// command = "",
|
|
|
|
// args = [],
|
|
|
|
// };
|
|
|
|
impl ExecConverter {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
ExecConverter {}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused_assignments)]
|
2019-09-18 20:09:57 -05:00
|
|
|
fn write(&self, v: &Val, w: &mut dyn Write) -> ConvertResult {
|
2018-06-20 17:19:17 -04:00
|
|
|
// We always expect the Val to be a Tuple.
|
|
|
|
if let &Tuple(ref fields) = v {
|
|
|
|
// We expect no more than three fields in our exec tuple.
|
|
|
|
if fields.len() > 3 {
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"Exec tuples must have no more than 3 fields",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
2019-02-19 16:36:19 -06:00
|
|
|
let mut env: Option<&Vec<(String, Rc<Val>)>> = None;
|
2018-06-20 17:19:17 -04:00
|
|
|
let mut command: Option<&str> = None;
|
|
|
|
let mut args: Option<&Vec<Rc<Val>>> = None;
|
|
|
|
for &(ref name, ref val) in fields.iter() {
|
|
|
|
// We require a command field in our exec tuple.
|
2019-02-19 16:36:19 -06:00
|
|
|
if name == "command" {
|
2018-06-20 17:19:17 -04:00
|
|
|
if command.is_some() {
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"There can only be one command field in an exec tuple",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
if let &Val::Str(ref s) = val.as_ref() {
|
|
|
|
command = Some(s);
|
|
|
|
continue;
|
|
|
|
}
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"The command field of an exec tuple must be a string",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
// We optionally allow an env field in our exec tuple.
|
2019-02-19 16:36:19 -06:00
|
|
|
if name == "env" {
|
2018-06-20 17:19:17 -04:00
|
|
|
if let &Val::Tuple(ref l) = val.as_ref() {
|
|
|
|
if env.is_some() {
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"There can only be one env field in an exec tuple",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
env = Some(l);
|
|
|
|
continue;
|
|
|
|
}
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"The env field of an exec tuple must be a list",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
// We optionally allow an args field in our exec tuple.
|
2019-02-19 16:36:19 -06:00
|
|
|
if name == "args" {
|
2018-06-20 17:19:17 -04:00
|
|
|
if let &Val::List(ref l) = val.as_ref() {
|
|
|
|
if args.is_some() {
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"There can only be one args field of an exec tuple",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
args = Some(l);
|
|
|
|
continue;
|
|
|
|
}
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"The args field of an exec tuple must be a list",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if command.is_none() {
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"An exec tuple must have a command field",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
// Okay if we have made it this far then we are ready to start creating our script.
|
|
|
|
let mut script = Cursor::new(vec![]);
|
|
|
|
// 1. First the script prefix line.
|
2018-12-06 12:46:47 -06:00
|
|
|
write!(script, "#!/usr/bin/env bash\n")?;
|
2018-06-20 17:19:17 -04:00
|
|
|
// 2. then some initial setup. for bash hygiene.
|
2018-12-06 12:46:47 -06:00
|
|
|
write!(script, "# Turn on unofficial Bash-Strict-Mode\n")?;
|
|
|
|
write!(script, "set -euo pipefail\n")?;
|
2018-06-20 17:19:17 -04:00
|
|
|
// 3. Then assign our environment variables
|
|
|
|
if let Some(env_list) = env {
|
|
|
|
for &(ref name, ref v) in env_list.iter() {
|
|
|
|
// We only allow string fields in our env tuple.
|
|
|
|
if let &Val::Str(ref s) = v.as_ref() {
|
2019-02-19 16:36:19 -06:00
|
|
|
write!(script, "{}=\"{}\"\n", name, s)?;
|
2018-06-20 17:19:17 -04:00
|
|
|
continue;
|
|
|
|
}
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"The env fields of an exec tuple must contain only string values",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
2018-12-06 12:46:47 -06:00
|
|
|
write!(script, "\n")?;
|
2018-06-20 17:19:17 -04:00
|
|
|
let flag_converter = convert::flags::FlagConverter::new();
|
|
|
|
// 4. Then construct our command line. (be sure to use exec)
|
2018-12-06 12:46:47 -06:00
|
|
|
write!(script, "exec {} ", command.unwrap())?;
|
2018-06-20 17:19:17 -04:00
|
|
|
if let Some(arg_list) = args {
|
|
|
|
for v in arg_list.iter() {
|
|
|
|
// We only allow tuples or strings in our args list.
|
|
|
|
match v.as_ref() {
|
|
|
|
&Val::Str(ref s) => {
|
2018-12-06 12:46:47 -06:00
|
|
|
write!(script, "{} ", s)?;
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
2018-12-06 12:46:47 -06:00
|
|
|
&Val::Tuple(_) => flag_converter.convert(v.clone(), &mut script)?,
|
2018-06-20 17:19:17 -04:00
|
|
|
_ => {
|
2019-02-20 20:45:33 -06:00
|
|
|
return Err(BuildError::new(
|
2018-06-20 17:19:17 -04:00
|
|
|
"Exec args must be a list of strings or tuples of strings.",
|
|
|
|
ErrorType::TypeFail,
|
2019-02-20 20:45:33 -06:00
|
|
|
)
|
|
|
|
.to_boxed());
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Put cursor to the beginning of our script so when we copy
|
|
|
|
// we copy the whole thing.
|
|
|
|
script.set_position(0);
|
2018-12-06 12:46:47 -06:00
|
|
|
std::io::copy(&mut script, w)?;
|
2018-06-20 17:19:17 -04:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2019-02-20 20:45:33 -06:00
|
|
|
Err(BuildError::new("Exec outputs must be of type Tuple", ErrorType::TypeFail).to_boxed())
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Converter for ExecConverter {
|
2019-09-18 20:09:57 -05:00
|
|
|
fn convert(&self, v: Rc<Val>, mut w: &mut dyn Write) -> ConvertResult {
|
2018-06-20 17:19:17 -04:00
|
|
|
self.write(&v, &mut w)
|
|
|
|
}
|
2018-08-15 18:22:05 -05:00
|
|
|
|
|
|
|
fn file_ext(&self) -> String {
|
|
|
|
String::from("sh")
|
|
|
|
}
|
2018-08-22 00:13:11 -05:00
|
|
|
|
|
|
|
fn description(&self) -> String {
|
|
|
|
"Convert ucg Vals into an bash script with \nenvironment variables set and command line arguments sent..".to_string()
|
|
|
|
}
|
2019-03-25 20:23:50 -04:00
|
|
|
|
|
|
|
#[allow(unused_must_use)]
|
|
|
|
fn help(&self) -> String {
|
2019-04-08 22:13:29 -05:00
|
|
|
include_str!("exec_help.txt").to_string()
|
2019-03-25 20:23:50 -04:00
|
|
|
}
|
2018-06-20 17:19:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod exec_test {
|
|
|
|
use super::*;
|
2018-12-31 10:10:19 -06:00
|
|
|
use crate::build::FileBuilder;
|
2018-12-06 12:23:52 -06:00
|
|
|
use crate::convert::traits::Converter;
|
2018-08-13 23:11:35 -05:00
|
|
|
|
2018-06-20 17:19:17 -04:00
|
|
|
use std;
|
|
|
|
use std::io::Cursor;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn convert_just_command_test() {
|
2018-12-13 19:03:22 -06:00
|
|
|
let i_paths = Vec::new();
|
2019-08-26 21:24:17 -05:00
|
|
|
let out: Vec<u8> = Vec::new();
|
|
|
|
let err: Vec<u8> = Vec::new();
|
|
|
|
let mut b = FileBuilder::new(std::env::current_dir().unwrap(), &i_paths, out, err);
|
2018-06-20 17:19:17 -04:00
|
|
|
let conv = ExecConverter::new();
|
|
|
|
b.eval_string(
|
|
|
|
"let script = {
|
|
|
|
command = \"/bin/echo\",
|
|
|
|
};",
|
2018-12-10 21:27:44 -06:00
|
|
|
)
|
|
|
|
.unwrap();
|
2018-06-20 17:19:17 -04:00
|
|
|
let result = b.get_out_by_name("script").unwrap();
|
|
|
|
let mut expected = "#!/usr/bin/env bash\n".to_string();
|
|
|
|
expected.push_str("# Turn on unofficial Bash-Strict-Mode\n");
|
|
|
|
expected.push_str("set -euo pipefail\n\n");
|
|
|
|
expected.push_str("exec /bin/echo ");
|
|
|
|
let mut buf = Cursor::new(vec![]);
|
|
|
|
conv.convert(result, &mut buf).unwrap();
|
|
|
|
assert_eq!(String::from_utf8_lossy(&buf.into_inner()), expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn convert_command_with_env_test() {
|
2018-12-13 19:03:22 -06:00
|
|
|
let i_paths = Vec::new();
|
2019-08-26 21:24:17 -05:00
|
|
|
let out: Vec<u8> = Vec::new();
|
|
|
|
let err: Vec<u8> = Vec::new();
|
|
|
|
let mut b = FileBuilder::new(std::env::current_dir().unwrap(), &i_paths, out, err);
|
2018-06-20 17:19:17 -04:00
|
|
|
let conv = ExecConverter::new();
|
|
|
|
b.eval_string(
|
|
|
|
"let script = {
|
|
|
|
command = \"/bin/echo\",
|
|
|
|
env = {
|
|
|
|
foo = \"bar\",
|
|
|
|
quux = \"baz\",
|
|
|
|
},
|
|
|
|
};",
|
2018-12-10 21:27:44 -06:00
|
|
|
)
|
|
|
|
.unwrap();
|
2018-06-20 17:19:17 -04:00
|
|
|
let result = b.get_out_by_name("script").unwrap();
|
|
|
|
let mut expected = "#!/usr/bin/env bash\n".to_string();
|
|
|
|
expected.push_str("# Turn on unofficial Bash-Strict-Mode\n");
|
|
|
|
expected.push_str("set -euo pipefail\n");
|
|
|
|
expected.push_str("foo=\"bar\"\n");
|
|
|
|
expected.push_str("quux=\"baz\"\n");
|
|
|
|
expected.push_str("\n");
|
|
|
|
expected.push_str("exec /bin/echo ");
|
|
|
|
let mut buf = Cursor::new(vec![]);
|
|
|
|
conv.convert(result, &mut buf).unwrap();
|
|
|
|
assert_eq!(String::from_utf8_lossy(&buf.into_inner()), expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn convert_command_with_arg_test() {
|
2018-12-13 19:03:22 -06:00
|
|
|
let i_paths = Vec::new();
|
2019-08-26 21:24:17 -05:00
|
|
|
let out: Vec<u8> = Vec::new();
|
|
|
|
let err: Vec<u8> = Vec::new();
|
|
|
|
let mut b = FileBuilder::new(std::env::current_dir().unwrap(), &i_paths, out, err);
|
2018-06-20 17:19:17 -04:00
|
|
|
let conv = ExecConverter::new();
|
|
|
|
b.eval_string(
|
|
|
|
"let script = {
|
|
|
|
command = \"/bin/echo\",
|
|
|
|
env = {
|
|
|
|
foo = \"bar\",
|
|
|
|
quux = \"baz\",
|
|
|
|
},
|
|
|
|
args = [
|
|
|
|
\"subcommand\",
|
|
|
|
{flag1 = 1},
|
|
|
|
],
|
|
|
|
};",
|
2018-12-10 21:27:44 -06:00
|
|
|
)
|
|
|
|
.unwrap();
|
2018-06-20 17:19:17 -04:00
|
|
|
let result = b.get_out_by_name("script").unwrap();
|
|
|
|
let mut expected = "#!/usr/bin/env bash\n".to_string();
|
|
|
|
expected.push_str("# Turn on unofficial Bash-Strict-Mode\n");
|
|
|
|
expected.push_str("set -euo pipefail\n");
|
|
|
|
expected.push_str("foo=\"bar\"\n");
|
|
|
|
expected.push_str("quux=\"baz\"\n");
|
|
|
|
expected.push_str("\n");
|
|
|
|
expected.push_str("exec /bin/echo subcommand --flag1 1 ");
|
|
|
|
let mut buf = Cursor::new(vec![]);
|
|
|
|
conv.convert(result, &mut buf).unwrap();
|
|
|
|
assert_eq!(String::from_utf8_lossy(&buf.into_inner()), expected);
|
|
|
|
}
|
|
|
|
}
|