use std::env;
use std::io::{self, Write};
use types::*;
use shell::status::*;
use shell::variables::Variables;
fn print_list(list: &VariableContext) {
let stdout = io::stdout();
let stdout = &mut stdout.lock();
for (key, value) in list {
let _ = stdout.write(key.as_bytes())
.and_then(|_| stdout.write_all(b" = "))
.and_then(|_| stdout.write_all(value.as_bytes()))
.and_then(|_| stdout.write_all(b"\n"));
}
}
enum Binding {
InvalidKey(Identifier),
ListEntries,
KeyOnly(Identifier),
KeyValue(Identifier, Value),
Math(Identifier, Operator, f32),
MathInvalid(Value)
}
enum Operator {
Plus,
Minus,
Divide,
Multiply
}
fn parse_assignment<'a, S: AsRef<str> + 'a>(args: &[S]) -> Binding {
let mut char_iter = args.iter().skip(1)
.map(|arg| arg.as_ref().chars())
.flat_map(|chars| chars);
let mut key = "".to_owned();
let mut found_key = false;
let mut operator = None;
while let Some(character) = char_iter.next() {
match character {
' ' if key.is_empty() => (),
' ' => found_key = true,
'+' => {
if char_iter.next() == Some('=') {
operator = Some(Operator::Plus);
found_key = true;
}
break
},
'-' => {
if char_iter.next() == Some('=') {
operator = Some(Operator::Minus);
found_key = true;
}
break
},
'*' => {
if char_iter.next() == Some('=') {
operator = Some(Operator::Multiply);
found_key = true;
}
break
},
'/' => {
if char_iter.next() == Some('=') {
operator = Some(Operator::Divide);
found_key = true;
}
break
},
'=' => {
found_key = true;
break
},
_ if !found_key => key.push(character),
_ => ()
}
}
let key: Identifier = key.into();
if !found_key && key.is_empty() {
Binding::ListEntries
} else {
let value: Value = char_iter.skip_while(|&x| x == ' ').collect();
if value.is_empty() {
Binding::KeyOnly(key)
} else if !Variables::is_valid_variable_name(&key) {
Binding::InvalidKey(key)
} else {
match operator {
Some(operator) => {
match value.parse::<f32>() {
Ok(value) => Binding::Math(key, operator, value),
Err(_) => Binding::MathInvalid(value)
}
},
None => Binding::KeyValue(key, value)
}
}
}
}
pub fn alias<'a, S: AsRef<str> + 'a>(vars: &mut Variables, args: &[S]) -> i32 {
match parse_assignment(args) {
Binding::InvalidKey(key) => {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: alias name, '{}', is invalid", key);
return FAILURE;
},
Binding::KeyValue(key, value) => { vars.aliases.insert(key, value); },
Binding::ListEntries => print_list(&vars.aliases),
Binding::KeyOnly(key) => {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: please provide value for alias '{}'", key);
return FAILURE;
},
_ => {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: invalid alias syntax");
return FAILURE;
}
}
SUCCESS
}
pub fn drop_alias<I: IntoIterator>(vars: &mut Variables, args: I) -> i32
where I::Item: AsRef<str>
{
let args = args.into_iter().collect::<Vec<I::Item>>();
if args.len() <= 1 {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: you must specify an alias name");
return FAILURE;
}
for alias in args.iter().skip(1) {
if vars.aliases.remove(alias.as_ref()).is_none() {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: undefined alias: {}", alias.as_ref());
return FAILURE;
}
}
SUCCESS
}
pub fn drop_variable<I: IntoIterator>(vars: &mut Variables, args: I) -> i32
where I::Item: AsRef<str>
{
let args = args.into_iter().collect::<Vec<I::Item>>();
if args.len() <= 1 {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: you must specify a variable name");
return FAILURE;
}
for variable in args.iter().skip(1) {
if vars.unset_var(variable.as_ref()).is_none() {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: undefined variable: {}", variable.as_ref());
return FAILURE;
}
}
SUCCESS
}
pub fn export_variable<'a, S: AsRef<str> + 'a>(vars: &mut Variables, args: &[S]) -> i32
{
match parse_assignment(args) {
Binding::InvalidKey(key) => {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: variable name, '{}', is invalid", key);
return FAILURE
},
Binding::KeyValue(key, value) => env::set_var(key, value),
Binding::KeyOnly(key) => {
if let Some(local_value) = vars.get_var(&key) {
env::set_var(key, local_value);
} else {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion: unknown variable, '{}'", key);
return FAILURE;
}
},
Binding::Math(key, operator, increment) => {
let value = vars.get_var(&key).unwrap_or_else(|| "".into());
match value.parse::<f32>() {
Ok(old_value) => match operator {
Operator::Plus => env::set_var(key, (old_value + increment).to_string()),
Operator::Minus => env::set_var(key, (old_value - increment).to_string()),
Operator::Multiply => env::set_var(key, (old_value * increment).to_string()),
Operator::Divide => env::set_var(key, (old_value / increment).to_string()),
},
Err(_) => {
let stderr = io::stderr();
let mut stderr = stderr.lock();
let _ = writeln!(stderr, "ion: original value, {}, is not a number", value);
return FAILURE;
}
}
},
Binding::MathInvalid(value) => {
let stderr = io::stderr();
let mut stderr = stderr.lock();
let _ = writeln!(stderr, "ion: supplied value, {}, is not a number", value);
return FAILURE;
},
_ => {
let stderr = io::stderr();
let _ = writeln!(&mut stderr.lock(), "ion usage: export KEY=VALUE");
return FAILURE;
}
}
SUCCESS
}
#[cfg(test)]
mod test {
use super::*;
use parser::{expand_string, ExpanderFunctions, Index, IndexEnd};
use shell::status::{FAILURE, SUCCESS};
use shell::directory_stack::DirectoryStack;
fn new_dir_stack() -> DirectoryStack {
DirectoryStack::new().unwrap()
}
#[test]
fn drop_deletes_variable() {
let mut variables = Variables::default();
variables.set_var("FOO", "BAR");
let return_status = drop_variable(&mut variables, vec!["drop", "FOO"]);
assert_eq!(SUCCESS, return_status);
let expanded = expand_string("$FOO", &get_expanders!(&variables, &new_dir_stack()), false).join("");
assert_eq!("", expanded);
}
#[test]
fn drop_fails_with_no_arguments() {
let mut variables = Variables::default();
let return_status = drop_variable(&mut variables, vec!["drop"]);
assert_eq!(FAILURE, return_status);
}
#[test]
fn drop_fails_with_undefined_variable() {
let mut variables = Variables::default();
let return_status = drop_variable(&mut variables, vec!["drop", "FOO"]);
assert_eq!(FAILURE, return_status);
}
}