Skip to content

Commit

Permalink
feat(lib): windows_call_cmd_with_env
Browse files Browse the repository at this point in the history
  • Loading branch information
EqualMa committed Sep 5, 2021
1 parent 56ceb91 commit a338c9d
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 15 deletions.
24 changes: 24 additions & 0 deletions runcc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,28 @@
name = "runcc"
version = "0.1.0"
edition = "2018"
authors = [
"Equal Ma <equalma@outlook.com>",
]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = "1"
serde_json = "1"
serde_yaml = "0.8"
toml = "0.5"
ron = "*"
ctrlc = "3"
clap = { version = "3.0.0-beta.4", optional = true }

[target.'cfg(windows)'.dependencies]
rand = "0.8"

[features]
default = ["cli"]
cli = ["clap"]

[[bin]]
name = "cargo-runcc"
required-features = ["cli"]
1 change: 1 addition & 0 deletions runcc/src/cli/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ impl Opts {
} else {
None
},
windows_call_cmd_with_env: Default::default(),
}
.into(),
))
Expand Down
36 changes: 32 additions & 4 deletions runcc/src/config/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ pub struct CommandConfig {
pub cwd: Option<String>,
}

#[non_exhaustive]
#[derive(Debug, Default)]
pub struct CommandConfigFromScriptOptions {
pub windows_call_cmd_with_env: super::WindowsCallCmdWithEnv,
}

impl CommandConfig {
pub fn from_script(script: &str) -> CommandConfig {
pub fn from_script(script: &str, options: &CommandConfigFromScriptOptions) -> CommandConfig {
let script = script.trim();

let (program, envs) = match_program_with_envs(script);
Expand All @@ -31,13 +37,30 @@ impl CommandConfig {
};
if program.contains(" ") {
if cfg!(target_os = "windows") {
CommandConfig {
let with_env = &options.windows_call_cmd_with_env;

let env_name = with_env.clone().try_into_env_name();

let (arg, env) = match env_name {
Some(env_name) => {
(format!("%{}%", env_name), Some((env_name, program.clone())))
}
None => (program.clone(), None),
};

let mut cmd = CommandConfig {
program: "cmd".to_string(),
args: Some(vec!["/C".to_string(), program.clone()]),
label: Some(program),
args: Some(vec!["/C".to_string(), arg]),
label: Some(program.clone()),
envs,
cwd: None,
};

if let Some(env) = env {
cmd.env(env);
}

cmd
} else {
CommandConfig {
program: "sh".to_string(),
Expand Down Expand Up @@ -121,4 +144,9 @@ impl CommandConfig {
Some(label) => label.len(),
}
}

pub fn env(&mut self, env: (String, String)) -> &mut Self {
self.envs.get_or_insert_with(|| vec![]).push(env);
self
}
}
19 changes: 10 additions & 9 deletions runcc/src/config/input/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ pub enum CommandConfigInput {
CommandConfig(CommandConfig),
}

impl Into<CommandConfig> for CommandConfigInput {
fn into(self) -> CommandConfig {
impl CommandConfigInput {
pub fn into_config(self, options: &CommandConfigFromScriptOptions) -> CommandConfig {
match self {
CommandConfigInput::Command(script) => CommandConfig::from_script(&script),
CommandConfigInput::Command(script) => CommandConfig::from_script(&script, options),
CommandConfigInput::ProgramAndArgs(mut names) => {
let program = if names.is_empty() {
String::new()
Expand All @@ -40,17 +40,18 @@ pub enum CommandConfigsInput {
LabeledCommands(HashMap<String, Option<CommandConfigInput>>),
}

impl Into<Vec<CommandConfig>> for CommandConfigsInput {
fn into(self) -> Vec<CommandConfig> {
impl CommandConfigsInput {
pub fn into_configs(self, options: &CommandConfigFromScriptOptions) -> Vec<CommandConfig> {
match self {
CommandConfigsInput::Commands(commands) => {
commands.into_iter().map(Into::into).collect()
}
CommandConfigsInput::Commands(commands) => commands
.into_iter()
.map(|cmd| cmd.into_config(options))
.collect(),
CommandConfigsInput::LabeledCommands(map) => map
.into_iter()
.map(|(label, command)| match command {
Some(command) => {
let mut command: CommandConfig = command.into();
let mut command: CommandConfig = command.into_config(options);
command.label = Some(label);

command
Expand Down
2 changes: 2 additions & 0 deletions runcc/src/config/input/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod command;
mod run;
mod win_cmd;

pub use command::*;
pub use run::*;
pub use win_cmd::*;
9 changes: 7 additions & 2 deletions runcc/src/config/input/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use std::cmp;
use std::collections::HashMap;

use super::super::{run::*, CommandConfig};
use super::super::{run::*, CommandConfig, CommandConfigFromScriptOptions};
use super::CommandConfigsInput;

#[non_exhaustive]
Expand All @@ -11,6 +11,8 @@ pub struct RunConfigInput {
pub commands: CommandConfigsInput,
pub max_label_length: Option<usize>,
pub envs: Option<HashMap<String, String>>,
#[serde(default)]
pub windows_call_cmd_with_env: super::WindowsCallCmdWithEnv,
}

impl Into<RunConfig> for RunConfigInput {
Expand All @@ -19,9 +21,12 @@ impl Into<RunConfig> for RunConfigInput {
commands,
max_label_length,
envs,
windows_call_cmd_with_env,
} = self;

let commands: Vec<CommandConfig> = commands.into();
let commands: Vec<CommandConfig> = commands.into_configs(&CommandConfigFromScriptOptions {
windows_call_cmd_with_env,
});

let real_max_label_length = commands
.iter()
Expand Down
108 changes: 108 additions & 0 deletions runcc/src/config/input/win_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt::Display};

pub struct InvalidEnvName(String);

impl Display for InvalidEnvName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid env name {}", self.0)
}
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(try_from = "String")]
#[serde(into = "String")]
pub struct EnvName(String);

impl TryFrom<String> for EnvName {
type Error = InvalidEnvName;

fn try_from(value: String) -> Result<Self, Self::Error> {
if EnvName::check_str(&value) {
Ok(Self(value))
} else {
Err(InvalidEnvName(value))
}
}
}

impl Into<String> for EnvName {
fn into(self) -> String {
self.0
}
}

impl EnvName {
pub fn to_string(self) -> String {
self.0
}

pub fn check_str(s: &str) -> bool {
s.len() > 0
&& s.chars().enumerate().all(|(i, c)| match c {
'a'..='z' | 'A'..='Z' => true,
'0'..='9' | '_' => i > 0,
_ => false,
})
}
}

#[non_exhaustive]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum WindowsCallCmdWithEnv {
Random,
EnvName(EnvName),
Disable,
}

#[cfg(windows)]
fn get_random_env_name() -> String {
use rand::{thread_rng, Rng};

let mut rng = thread_rng();

(0..8).map(|_| rng.gen_range('A'..='Z')).collect()
}

#[cfg(not(windows))]
fn get_random_env_name() -> String {
"RANDNAME".to_string()
}

impl WindowsCallCmdWithEnv {
pub fn try_into_env_name(self) -> Option<String> {
match self {
Self::Random => Some(format!("RUNCC_WIN_CMD__{}", get_random_env_name())),
Self::EnvName(env_name) => Some(env_name.to_string()),
Self::Disable => None,
}
}
}

impl Default for WindowsCallCmdWithEnv {
fn default() -> Self {
Self::Random
}
}

impl Display for WindowsCallCmdWithEnv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}

/// strictly validate env name [a-zA-Z][a-zA-Z0-9_]*
#[cfg(test)]
mod tests {
use super::EnvName;

#[test]
fn test_is_valid_env_name() {
assert!(EnvName::check_str("MY_ENV_0123_"));

assert!(!EnvName::check_str(""));
assert!(!EnvName::check_str(" "));
assert!(!EnvName::check_str("123abc"));
assert!(!EnvName::check_str("_MY_ENV"));
}
}

0 comments on commit a338c9d

Please sign in to comment.