use std::collections::HashMap;
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::policy::{ShellIdentity, VariableAttributes};
use crate::shell;
pub type BuiltinCallback =
dyn for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct BuiltinProperties {
special: bool,
}
impl BuiltinProperties {
pub const fn regular() -> Self {
Self { special: false }
}
pub const fn special() -> Self {
Self { special: true }
}
pub const fn is_special(self) -> bool {
self.special
}
}
impl Default for BuiltinProperties {
fn default() -> Self {
Self::regular()
}
}
#[derive(Clone)]
pub(crate) enum RegisteredBuiltin {
Standard {
kind: crate::shell::StandardBuiltin,
properties: BuiltinProperties,
},
Custom {
properties: BuiltinProperties,
handler: Arc<BuiltinCallback>,
},
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PortableBuiltinEntry {
name: String,
kind: crate::shell::StandardBuiltin,
properties: BuiltinProperties,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PortableBuiltinRegistry {
entries: Vec<PortableBuiltinEntry>,
}
impl RegisteredBuiltin {
pub(crate) fn standard(
kind: crate::shell::StandardBuiltin,
properties: BuiltinProperties,
) -> Self {
Self::Standard { kind, properties }
}
pub(crate) fn custom(properties: BuiltinProperties, handler: Arc<BuiltinCallback>) -> Self {
Self::Custom {
properties,
handler,
}
}
pub(crate) fn properties(&self) -> BuiltinProperties {
match self {
Self::Standard { properties, .. } | Self::Custom { properties, .. } => *properties,
}
}
}
#[derive(Clone)]
pub(crate) struct BuiltinRegistry {
entries: HashMap<String, RegisteredBuiltin>,
}
impl BuiltinRegistry {
pub fn empty() -> Self {
Self {
entries: HashMap::new(),
}
}
pub fn standard() -> Self {
crate::shell::standard_builtin_registry()
}
pub(crate) fn contains(&self, name: &str) -> bool {
self.entries.contains_key(name)
}
pub(crate) fn lookup(&self, name: &str) -> Option<RegisteredBuiltin> {
self.entries.get(name).cloned()
}
pub(crate) fn remove(&mut self, name: &str) -> Option<RegisteredBuiltin> {
self.entries.remove(name)
}
pub(crate) fn insert_standard(
&mut self,
name: impl Into<String>,
kind: crate::shell::StandardBuiltin,
properties: BuiltinProperties,
) -> Option<RegisteredBuiltin> {
self.entries
.insert(name.into(), RegisteredBuiltin::standard(kind, properties))
}
pub(crate) fn insert_custom(
&mut self,
name: impl Into<String>,
properties: BuiltinProperties,
handler: Arc<BuiltinCallback>,
) -> Option<RegisteredBuiltin> {
self.entries
.insert(name.into(), RegisteredBuiltin::custom(properties, handler))
}
pub(crate) fn names(&self) -> Vec<String> {
let mut names = self.entries.keys().cloned().collect::<Vec<_>>();
names.sort();
names
}
pub(crate) fn portable_background_checkpoint(&self) -> (PortableBuiltinRegistry, Vec<String>) {
let mut entries = Vec::with_capacity(self.entries.len());
let mut non_portable_names = Vec::new();
for (name, registered) in &self.entries {
let RegisteredBuiltin::Standard { kind, properties } = registered else {
non_portable_names.push(name.clone());
continue;
};
entries.push(PortableBuiltinEntry {
name: name.clone(),
kind: *kind,
properties: *properties,
});
}
entries.sort_by(|left, right| left.name.cmp(&right.name));
non_portable_names.sort();
(PortableBuiltinRegistry { entries }, non_portable_names)
}
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(crate) fn from_portable_background_checkpoint(checkpoint: PortableBuiltinRegistry) -> Self {
let mut registry = Self::empty();
for entry in checkpoint.entries {
let _ = registry.insert_standard(entry.name, entry.kind, entry.properties);
}
registry
}
}
impl Default for BuiltinRegistry {
fn default() -> Self {
Self::standard()
}
}
impl fmt::Debug for BuiltinRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut names = self.entries.keys().collect::<Vec<_>>();
names.sort();
f.debug_struct("BuiltinRegistry")
.field("builtin_names", &names)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BuildError {
BuiltinConflict(String),
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BuiltinConflict(name) => {
write!(
f,
"builtin registration conflicts with existing builtin: {name}"
)
}
}
}
}
impl std::error::Error for BuildError {}
pub struct BuiltinHost<'a> {
state: &'a mut shell::ShellState,
}
impl<'a> BuiltinHost<'a> {
pub(crate) fn new(state: &'a mut shell::ShellState) -> Self {
Self { state }
}
pub fn env_get(&self, key: &str) -> Option<&str> {
self.state.env_get(key)
}
pub fn identity(&self) -> &ShellIdentity {
&self.state.definition.identity
}
pub fn shell_name(&self) -> &str {
self.identity().name()
}
pub fn env_set(
&mut self,
key: &str,
value: impl Into<String>,
attrib: VariableAttributes,
) -> bool {
self.state.env_set(key, value.into(), attrib.bits())
}
pub fn current_dir(&self) -> &Path {
&self.state.path_state.cwd
}
pub fn set_current_dir<P: Into<PathBuf>>(&mut self, cwd: P) {
self.state.path_state.cwd = cwd.into();
self.state.env_set_internal(
"PWD",
self.state.path_state.cwd.display().to_string(),
shell::VAR_EXPORT,
);
}
pub fn write_stdout(&self, message: &str) -> io::Result<()> {
self.state.stdout_fd.write_str(message)
}
pub fn write_stdout_bytes(&self, message: &[u8]) -> io::Result<()> {
self.state.stdout_fd.write_all(message)
}
pub fn write_stdout_line(&self, message: &str) -> io::Result<()> {
self.state.stdout_fd.write_line(message)
}
pub fn write_stderr(&self, message: &str) -> io::Result<()> {
self.state.stderr_fd.write_line(message)
}
}