Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
@@ -0,0 +1,676 @@
|
||||
//! Implements a shell variable environment.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map;
|
||||
|
||||
use crate::Shell;
|
||||
use crate::error;
|
||||
use crate::extensions;
|
||||
use crate::variables::{self, ShellValue, ShellValueUnsetType, ShellVariable};
|
||||
|
||||
/// Represents the policy for looking up variables in a shell environment.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum EnvironmentLookup {
|
||||
/// Look anywhere.
|
||||
Anywhere,
|
||||
/// Look only in the global scope.
|
||||
OnlyInGlobal,
|
||||
/// Look only in the current local scope.
|
||||
OnlyInCurrentLocal,
|
||||
/// Look only in local scopes.
|
||||
OnlyInLocal,
|
||||
}
|
||||
|
||||
/// Represents a shell environment scope.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum EnvironmentScope {
|
||||
/// Scope local to a function instance
|
||||
Local,
|
||||
/// Globals
|
||||
Global,
|
||||
/// Transient overrides for a command invocation
|
||||
Command,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EnvironmentScope {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Local => write!(f, "local"),
|
||||
Self::Global => write!(f, "global"),
|
||||
Self::Command => write!(f, "command"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A guard that pushes a scope onto a shell environment and pops it when dropped.
|
||||
pub(crate) struct ScopeGuard<'a, SE: extensions::ShellExtensions> {
|
||||
scope_type: EnvironmentScope,
|
||||
shell: &'a mut crate::Shell<SE>,
|
||||
detached: bool,
|
||||
}
|
||||
|
||||
impl<'a, SE: extensions::ShellExtensions> ScopeGuard<'a, SE> {
|
||||
/// Creates a new scope guard, pushing the given scope type onto the environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `shell` - The shell whose environment to modify.
|
||||
/// * `scope_type` - The type of scope to push.
|
||||
pub fn new(shell: &'a mut crate::Shell<SE>, scope_type: EnvironmentScope) -> Self {
|
||||
shell.env_mut().push_scope(scope_type);
|
||||
Self {
|
||||
scope_type,
|
||||
shell,
|
||||
detached: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the shell.
|
||||
pub const fn shell(&mut self) -> &mut crate::Shell<SE> {
|
||||
self.shell
|
||||
}
|
||||
|
||||
/// Detaches the guard, preventing it from popping the scope on drop.
|
||||
pub const fn detach(&mut self) {
|
||||
self.detached = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<SE: extensions::ShellExtensions> Drop for ScopeGuard<'_, SE> {
|
||||
fn drop(&mut self) {
|
||||
if !self.detached {
|
||||
let _ = self.shell.env_mut().pop_scope(self.scope_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the shell variable environment, composed of a stack of scopes.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ShellEnvironment {
|
||||
/// Stack of scopes, with the top of the stack being the current scope.
|
||||
scopes: Vec<(EnvironmentScope, ShellVariableMap)>,
|
||||
/// Whether or not to auto-export variables on creation or modification.
|
||||
export_variables_on_modification: bool,
|
||||
/// Count of total entries (may include duplicates with shadowed variables).
|
||||
entry_count: usize,
|
||||
}
|
||||
|
||||
impl Default for ShellEnvironment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellEnvironment {
|
||||
/// Returns a new shell environment.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scopes: vec![(EnvironmentScope::Global, ShellVariableMap::default())],
|
||||
export_variables_on_modification: false,
|
||||
entry_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes a new scope of the given type onto the environment's scope stack.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `scope_type` - The type of scope to push.
|
||||
pub fn push_scope(&mut self, scope_type: EnvironmentScope) {
|
||||
self.scopes.push((scope_type, ShellVariableMap::default()));
|
||||
}
|
||||
|
||||
/// Pops the top-most scope off the environment's scope stack.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `expected_scope_type` - The type of scope that is expected to be atop the stack.
|
||||
pub fn pop_scope(&mut self, expected_scope_type: EnvironmentScope) -> Result<(), error::Error> {
|
||||
// TODO(env): Should we panic instead on failure? It's effectively a broken invariant.
|
||||
match self.scopes.pop() {
|
||||
Some((actual_scope_type, _)) if actual_scope_type == expected_scope_type => Ok(()),
|
||||
Some((actual_scope_type, _)) => Err(error::ErrorKind::UnexpectedScopeType {
|
||||
expected: expected_scope_type,
|
||||
actual: actual_scope_type,
|
||||
}
|
||||
.into()),
|
||||
None => Err(error::ErrorKind::MissingScope.into()),
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Iterators/Getters
|
||||
//
|
||||
|
||||
/// Returns an iterator over all exported variables defined in the variable.
|
||||
pub fn iter_exported(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
|
||||
// We won't actually need to store all entries, but we expect it should be
|
||||
// within the same order.
|
||||
let mut visible_vars: HashMap<&String, &ShellVariable> =
|
||||
HashMap::with_capacity(self.entry_count);
|
||||
|
||||
for (_, var_map) in self.scopes.iter().rev() {
|
||||
for (name, var) in var_map.iter().filter(|(_, v)| v.is_exported()) {
|
||||
// Only insert the variable if it hasn't been seen yet.
|
||||
if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
|
||||
entry.insert(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visible_vars.into_iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all the variables defined in the environment.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
|
||||
self.iter_using_policy(EnvironmentLookup::Anywhere)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all the variables defined in the environment,
|
||||
/// using the given lookup policy.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lookup_policy` - The policy to use when looking up variables.
|
||||
pub fn iter_using_policy(
|
||||
&self,
|
||||
lookup_policy: EnvironmentLookup,
|
||||
) -> impl Iterator<Item = (&String, &ShellVariable)> {
|
||||
// We won't actually need to store all entries, but we expect it should be
|
||||
// within the same order.
|
||||
let mut visible_vars: HashMap<&String, &ShellVariable> =
|
||||
HashMap::with_capacity(self.entry_count);
|
||||
|
||||
let mut local_count = 0;
|
||||
for (scope_type, var_map) in self.scopes.iter().rev() {
|
||||
if matches!(scope_type, EnvironmentScope::Local) {
|
||||
local_count += 1;
|
||||
}
|
||||
|
||||
match lookup_policy {
|
||||
EnvironmentLookup::Anywhere => (),
|
||||
EnvironmentLookup::OnlyInGlobal => {
|
||||
if !matches!(scope_type, EnvironmentScope::Global) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
EnvironmentLookup::OnlyInCurrentLocal => {
|
||||
if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
EnvironmentLookup::OnlyInLocal => {
|
||||
if !matches!(scope_type, EnvironmentScope::Local) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (name, var) in var_map.iter() {
|
||||
// Only insert the variable if it hasn't been seen yet.
|
||||
if let hash_map::Entry::Vacant(entry) = visible_vars.entry(name) {
|
||||
entry.insert(var);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(scope_type, EnvironmentScope::Local)
|
||||
&& matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
visible_vars.into_iter()
|
||||
}
|
||||
|
||||
/// Tries to retrieve an immutable reference to the variable with the given name
|
||||
/// in the environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to retrieve.
|
||||
pub fn get<S: AsRef<str>>(&self, name: S) -> Option<(EnvironmentScope, &ShellVariable)> {
|
||||
// Look through scopes, from the top of the stack on down.
|
||||
for (scope_type, map) in self.scopes.iter().rev() {
|
||||
if let Some(var) = map.get(name.as_ref()) {
|
||||
return Some((*scope_type, var));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Tries to retrieve a mutable reference to the variable with the given name
|
||||
/// in the environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to retrieve.
|
||||
pub fn get_mut<S: AsRef<str>>(
|
||||
&mut self,
|
||||
name: S,
|
||||
) -> Option<(EnvironmentScope, &mut ShellVariable)> {
|
||||
// Look through scopes, from the top of the stack on down.
|
||||
for (scope_type, map) in self.scopes.iter_mut().rev() {
|
||||
if let Some(var) = map.get_mut(name.as_ref()) {
|
||||
return Some((*scope_type, var));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Tries to retrieve the string value of the variable with the given name in the
|
||||
/// environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to retrieve.
|
||||
/// * `shell` - The shell owning the environment.
|
||||
pub fn get_str<S: AsRef<str>, SE: extensions::ShellExtensions>(
|
||||
&self,
|
||||
name: S,
|
||||
shell: &Shell<SE>,
|
||||
) -> Option<Cow<'_, str>> {
|
||||
self.get(name.as_ref())
|
||||
.map(|(_, v)| v.value().to_cow_str(shell))
|
||||
}
|
||||
|
||||
/// Checks if a variable of the given name is set in the environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to check.
|
||||
pub fn is_set<S: AsRef<str>>(&self, name: S) -> bool {
|
||||
if let Some((_, var)) = self.get(name) {
|
||||
!matches!(var.value(), ShellValue::Unset(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Setters
|
||||
//
|
||||
|
||||
/// Tries to unset the variable with the given name in the environment, returning
|
||||
/// whether or not such a variable existed.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to unset.
|
||||
pub fn unset(&mut self, name: &str) -> Result<Option<ShellVariable>, error::Error> {
|
||||
let mut local_count = 0;
|
||||
for (scope_type, map) in self.scopes.iter_mut().rev() {
|
||||
if matches!(scope_type, EnvironmentScope::Local) {
|
||||
local_count += 1;
|
||||
}
|
||||
|
||||
let unset_result = Self::try_unset_in_map(map, name)?;
|
||||
|
||||
if unset_result.is_some() {
|
||||
// If we end up finding a local in the top-most local frame, then we replace
|
||||
// it with a placeholder.
|
||||
if matches!(scope_type, EnvironmentScope::Local) && local_count == 1 {
|
||||
map.set(
|
||||
name,
|
||||
ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped)),
|
||||
);
|
||||
} else if self.entry_count > 0 {
|
||||
// Entry count should never be 0 here, but we're being defensive.
|
||||
self.entry_count -= 1;
|
||||
}
|
||||
|
||||
return Ok(unset_result);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Tries to unset an array element from the environment, using the given name and
|
||||
/// element index for lookup. Returns whether or not an element was unset.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the array variable to unset an element from.
|
||||
/// * `index` - The index of the element to unset.
|
||||
pub fn unset_index(&mut self, name: &str, index: &str) -> Result<bool, error::Error> {
|
||||
if let Some((_, var)) = self.get_mut(name) {
|
||||
var.unset_index(index)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_unset_in_map(
|
||||
map: &mut ShellVariableMap,
|
||||
name: &str,
|
||||
) -> Result<Option<ShellVariable>, error::Error> {
|
||||
match map.get(name).map(|v| v.is_readonly()) {
|
||||
Some(true) => Err(error::ErrorKind::ReadonlyVariable.into()),
|
||||
Some(false) => Ok(map.unset(name)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to retrieve an immutable reference to a variable from the environment,
|
||||
/// using the given name and lookup policy.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to retrieve.
|
||||
/// * `lookup_policy` - The policy to use when looking up the variable.
|
||||
pub fn get_using_policy<N: AsRef<str>>(
|
||||
&self,
|
||||
name: N,
|
||||
lookup_policy: EnvironmentLookup,
|
||||
) -> Option<&ShellVariable> {
|
||||
let mut local_count = 0;
|
||||
for (scope_type, var_map) in self.scopes.iter().rev() {
|
||||
if matches!(scope_type, EnvironmentScope::Local) {
|
||||
local_count += 1;
|
||||
}
|
||||
|
||||
match lookup_policy {
|
||||
EnvironmentLookup::Anywhere => (),
|
||||
EnvironmentLookup::OnlyInGlobal => {
|
||||
if !matches!(scope_type, EnvironmentScope::Global) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
EnvironmentLookup::OnlyInCurrentLocal => {
|
||||
if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
EnvironmentLookup::OnlyInLocal => {
|
||||
if !matches!(scope_type, EnvironmentScope::Local) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(var) = var_map.get(name.as_ref()) {
|
||||
return Some(var);
|
||||
}
|
||||
|
||||
if matches!(scope_type, EnvironmentScope::Local)
|
||||
&& matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Tries to retrieve a mutable reference to a variable from the environment,
|
||||
/// using the given name and lookup policy.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to retrieve.
|
||||
/// * `lookup_policy` - The policy to use when looking up the variable.
|
||||
pub fn get_mut_using_policy<N: AsRef<str>>(
|
||||
&mut self,
|
||||
name: N,
|
||||
lookup_policy: EnvironmentLookup,
|
||||
) -> Option<&mut ShellVariable> {
|
||||
let mut local_count = 0;
|
||||
for (scope_type, var_map) in self.scopes.iter_mut().rev() {
|
||||
if matches!(scope_type, EnvironmentScope::Local) {
|
||||
local_count += 1;
|
||||
}
|
||||
|
||||
match lookup_policy {
|
||||
EnvironmentLookup::Anywhere => (),
|
||||
EnvironmentLookup::OnlyInGlobal => {
|
||||
if !matches!(scope_type, EnvironmentScope::Global) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
EnvironmentLookup::OnlyInCurrentLocal => {
|
||||
if !(matches!(scope_type, EnvironmentScope::Local) && local_count == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
EnvironmentLookup::OnlyInLocal => {
|
||||
if !matches!(scope_type, EnvironmentScope::Local) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(var) = var_map.get_mut(name.as_ref()) {
|
||||
return Some(var);
|
||||
}
|
||||
|
||||
if matches!(scope_type, EnvironmentScope::Local)
|
||||
&& matches!(lookup_policy, EnvironmentLookup::OnlyInCurrentLocal)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Update a variable in the environment, or add it if it doesn't already exist.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to update or add.
|
||||
/// * `value` - The value to assign to the variable.
|
||||
/// * `updater` - A function to call to update the variable after assigning the value.
|
||||
/// * `lookup_policy` - The policy to use when looking up the variable.
|
||||
/// * `scope_if_creating` - The scope to create the variable in if it doesn't already exist.
|
||||
pub fn update_or_add<N: Into<String>>(
|
||||
&mut self,
|
||||
name: N,
|
||||
value: variables::ShellValueLiteral,
|
||||
updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
|
||||
lookup_policy: EnvironmentLookup,
|
||||
scope_if_creating: EnvironmentScope,
|
||||
) -> Result<(), error::Error> {
|
||||
let name = name.into();
|
||||
|
||||
let auto_export = self.export_variables_on_modification;
|
||||
if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
|
||||
var.assign(value, false)?;
|
||||
if auto_export {
|
||||
var.export();
|
||||
}
|
||||
updater(var)
|
||||
} else {
|
||||
let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
|
||||
var.assign(value, false)?;
|
||||
if auto_export {
|
||||
var.export();
|
||||
}
|
||||
updater(&mut var)?;
|
||||
|
||||
self.add(name, var, scope_if_creating)
|
||||
}
|
||||
}
|
||||
|
||||
/// Update an array element in the environment, or add it if it doesn't already exist.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to update or add.
|
||||
/// * `index` - The index of the element to update or add.
|
||||
/// * `value` - The value to assign to the variable.
|
||||
/// * `updater` - A function to call to update the variable after assigning the value.
|
||||
/// * `lookup_policy` - The policy to use when looking up the variable.
|
||||
/// * `scope_if_creating` - The scope to create the variable in if it doesn't already exist.
|
||||
pub fn update_or_add_array_element<N: Into<String>>(
|
||||
&mut self,
|
||||
name: N,
|
||||
index: String,
|
||||
value: String,
|
||||
updater: impl Fn(&mut ShellVariable) -> Result<(), error::Error>,
|
||||
lookup_policy: EnvironmentLookup,
|
||||
scope_if_creating: EnvironmentScope,
|
||||
) -> Result<(), error::Error> {
|
||||
let name = name.into();
|
||||
|
||||
if let Some(var) = self.get_mut_using_policy(&name, lookup_policy) {
|
||||
var.assign_at_index(index, value, false)?;
|
||||
updater(var)
|
||||
} else {
|
||||
let mut var = ShellVariable::new(ShellValue::Unset(ShellValueUnsetType::Untyped));
|
||||
var.assign(
|
||||
variables::ShellValueLiteral::Array(variables::ArrayLiteral(vec![(
|
||||
Some(index),
|
||||
value,
|
||||
)])),
|
||||
false,
|
||||
)?;
|
||||
updater(&mut var)?;
|
||||
|
||||
self.add(name, var, scope_if_creating)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a variable to the environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to add.
|
||||
/// * `var` - The variable to add.
|
||||
/// * `target_scope` - The scope to add the variable to.
|
||||
pub fn add<N: Into<String>>(
|
||||
&mut self,
|
||||
name: N,
|
||||
mut var: ShellVariable,
|
||||
target_scope: EnvironmentScope,
|
||||
) -> Result<(), error::Error> {
|
||||
if self.export_variables_on_modification {
|
||||
var.export();
|
||||
}
|
||||
|
||||
for (scope_type, map) in self.scopes.iter_mut().rev() {
|
||||
if *scope_type == target_scope {
|
||||
let prev_var = map.set(name, var);
|
||||
if prev_var.is_none() {
|
||||
self.entry_count += 1;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(error::ErrorKind::MissingScopeForNewVariable.into())
|
||||
}
|
||||
|
||||
/// Sets a global variable in the environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to set.
|
||||
/// * `var` - The variable to set.
|
||||
pub fn set_global<N: Into<String>>(
|
||||
&mut self,
|
||||
name: N,
|
||||
var: ShellVariable,
|
||||
) -> Result<(), error::Error> {
|
||||
self.add(name, var, EnvironmentScope::Global)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a map from names to shell variables.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ShellVariableMap {
|
||||
variables: HashMap<String, ShellVariable>,
|
||||
}
|
||||
|
||||
impl ShellVariableMap {
|
||||
//
|
||||
// Iterators/Getters
|
||||
//
|
||||
|
||||
/// Returns an iterator over all the variables in the map.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&String, &ShellVariable)> {
|
||||
self.variables.iter()
|
||||
}
|
||||
|
||||
/// Tries to retrieve an immutable reference to the variable with the given name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to retrieve.
|
||||
pub fn get(&self, name: &str) -> Option<&ShellVariable> {
|
||||
self.variables.get(name)
|
||||
}
|
||||
|
||||
/// Tries to retrieve a mutable reference to the variable with the given name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to retrieve.
|
||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut ShellVariable> {
|
||||
self.variables.get_mut(name)
|
||||
}
|
||||
|
||||
//
|
||||
// Setters
|
||||
//
|
||||
|
||||
/// Tries to unset the variable with the given name, returning the removed
|
||||
/// variable or None if it was not already set.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to unset.
|
||||
pub fn unset(&mut self, name: &str) -> Option<ShellVariable> {
|
||||
self.variables.remove(name)
|
||||
}
|
||||
|
||||
/// Sets a variable in the map.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the variable to set.
|
||||
/// * `var` - The variable to set.
|
||||
pub fn set<N: Into<String>>(&mut self, name: N, var: ShellVariable) -> Option<ShellVariable> {
|
||||
self.variables.insert(name.into(), var)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given name is a valid variable name.
|
||||
pub fn valid_variable_name(s: &str) -> bool {
|
||||
let mut cs = s.chars();
|
||||
match cs.next() {
|
||||
Some(c) if c.is_ascii_alphabetic() || c == '_' => {
|
||||
cs.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
}
|
||||
Some(_) | None => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_valid_variable_name() {
|
||||
assert!(!valid_variable_name(""));
|
||||
assert!(!valid_variable_name("1"));
|
||||
assert!(!valid_variable_name(" a"));
|
||||
assert!(!valid_variable_name(" "));
|
||||
|
||||
assert!(valid_variable_name("_"));
|
||||
assert!(valid_variable_name("_a"));
|
||||
assert!(valid_variable_name("_1"));
|
||||
assert!(valid_variable_name("_a1"));
|
||||
assert!(valid_variable_name("a"));
|
||||
assert!(valid_variable_name("A"));
|
||||
assert!(valid_variable_name("a1"));
|
||||
assert!(valid_variable_name("A1"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user