use std::collections::HashMap;
use std::fmt;
#[cfg(feature = "frontend")]
use std::io;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crate::advanced::{PreparedProgram as AdvancedPreparedProgram, SessionState};
use crate::ast::{Command as AstCommand, ParameterOp, Program, Word};
pub use crate::builtin::BuildError;
use crate::builtin::{BuiltinHost, BuiltinProperties, BuiltinRegistry, PortableBuiltinRegistry};
#[cfg(feature = "frontend")]
use crate::parser::{Parser, configure_parser_for_language};
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
use crate::policy::CommandPolicy;
use crate::policy::{BackgroundLauncher, PortableCommandPolicy};
pub use crate::policy::{
ShellIdentity, ShellLanguage, ShellOptionSchema, ShellOptionSpec, ShellOptions,
ShellSecurityPolicy, StartupPolicy, StartupSources, VariableAttributes,
};
use crate::runtime::{Runtime, fd::FileDescriptor};
use crate::shell;
pub(crate) type HistoryAppender = dyn Fn(&str) + Send + Sync + 'static;
pub type PreparedProgram<'ast> = AdvancedPreparedProgram<'ast>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StdioConfig {
pub stdin: FileDescriptor,
pub stdout: FileDescriptor,
pub stderr: FileDescriptor,
}
impl Default for StdioConfig {
fn default() -> Self {
Self {
stdin: FileDescriptor::STDIN,
stdout: FileDescriptor::STDOUT,
stderr: FileDescriptor::STDERR,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamedFileDescriptor {
pub name: String,
pub child_fd: i32,
pub parent_fd: FileDescriptor,
}
impl NamedFileDescriptor {
pub fn new(name: impl Into<String>, child_fd: i32, parent_fd: FileDescriptor) -> Self {
Self {
name: name.into(),
child_fd,
parent_fd,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticKind {
Error,
Warning,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticCategory {
Input,
Expansion,
CommandLookup,
Execution,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShellDiagnostic {
pub kind: DiagnosticKind,
pub category: DiagnosticCategory,
pub code: &'static str,
pub message: String,
pub source: Option<String>,
pub range: Option<crate::ast::Range>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeferredWorkKind {
CommandDispatch,
CompoundCommand,
CommandSubstitution,
ParameterExpansion,
ArithmeticExpansion,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum DeferredWorkDetail {
CommandDispatch { command: AstCommand },
CompoundCommand { command: AstCommand },
CommandSubstitution { word: Word },
ParameterExpansion { word: Word, op: ParameterOp },
ArithmeticExpansion { word: Word },
}
#[derive(Debug, Clone, PartialEq)]
pub enum TraceEvent {
RunStarted {
timestamp: String,
pid: u32,
command_id: String,
raw_command: Option<String>,
canonical_command: Option<String>,
},
RunFinished {
timestamp: String,
pid: u32,
command_id: String,
status: i32,
},
BackgroundJobStarted {
timestamp: String,
pid: u32,
command_id: String,
raw_command: Option<String>,
canonical_command: Option<String>,
job_id: u32,
background_pid: Option<u32>,
},
BackgroundJobFinished {
timestamp: String,
pid: u32,
command_id: String,
status: i32,
job_id: u32,
background_pid: Option<u32>,
},
DeferredWorkStarted {
timestamp: String,
pid: u32,
command_id: String,
work_id: String,
kind: DeferredWorkKind,
detail: DeferredWorkDetail,
},
DeferredWorkFinished {
timestamp: String,
pid: u32,
command_id: String,
work_id: String,
kind: DeferredWorkKind,
detail: DeferredWorkDetail,
status: i32,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct RunOutcome {
pub status: i32,
pub exit_code: Option<i32>,
pub diagnostics: Vec<ShellDiagnostic>,
pub trace: Vec<TraceEvent>,
}
impl RunOutcome {
fn from_state(
status: i32,
state: &shell::ShellState,
diagnostics: Vec<ShellDiagnostic>,
trace: Vec<TraceEvent>,
) -> Self {
Self {
status,
exit_code: (state.exit_code >= 0).then_some(state.exit_code),
diagnostics,
trace,
}
}
#[cfg(feature = "frontend")]
pub(crate) fn empty_from_state(status: i32, state: &shell::ShellState) -> Self {
Self::from_state(status, state, Vec::new(), Vec::new())
}
#[cfg(feature = "frontend")]
pub(crate) fn set_exit_code(&mut self, exit_code: Option<i32>) {
self.exit_code = exit_code;
}
pub(crate) fn update_last_run_finished_status(&mut self, final_status: i32) {
if let Some(TraceEvent::RunFinished { status, .. }) = self
.trace
.iter_mut()
.rev()
.find(|event| matches!(event, TraceEvent::RunFinished { .. }))
{
*status = final_status;
}
}
}
#[derive(Debug, Default)]
pub(crate) struct OutcomeCapture {
pub(crate) diagnostics: Vec<ShellDiagnostic>,
pub(crate) trace: Vec<TraceEvent>,
}
pub(crate) type OutcomeCaptureHandle = Arc<Mutex<OutcomeCapture>>;
pub(crate) fn capture_outcome_for_state(
state: &mut shell::ShellState,
run: impl FnOnce(&mut shell::ShellState) -> i32,
) -> RunOutcome {
let capture = Arc::new(Mutex::new(OutcomeCapture::default()));
state.push_outcome_capture(Arc::clone(&capture));
let status = run(state);
let _ = state.pop_outcome_capture();
let capture = capture
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
RunOutcome::from_state(
status,
state,
capture.diagnostics.clone(),
capture.trace.clone(),
)
}
#[derive(Debug, Clone, Default)]
struct SessionSeed {
argv0: Option<String>,
positional_parameters: Vec<String>,
functions: HashMap<String, AstCommand>,
aliases: HashMap<String, String>,
raw_fds: HashMap<i32, FileDescriptor>,
named_fds: HashMap<String, i32>,
}
impl SessionSeed {
fn frame(&self, default_argv0: &str) -> Vec<String> {
let mut frame = vec![self.argv0(default_argv0).to_string()];
frame.extend(self.positional_parameters.iter().cloned());
frame
}
fn argv0<'a>(&'a self, default_argv0: &'a str) -> &'a str {
self.argv0.as_deref().unwrap_or(default_argv0)
}
fn set_frame<I, S>(&mut self, frame: I)
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut frame = frame.into_iter().map(Into::into);
self.argv0 = frame.next();
self.positional_parameters = frame.collect();
}
fn set_argv0(&mut self, argv0: impl Into<String>) {
self.argv0 = Some(argv0.into());
}
fn set_positional_parameters(&mut self, params: Vec<String>) {
self.positional_parameters = params;
}
fn aliases(&self) -> Vec<(String, String)> {
let mut aliases: Vec<_> = self
.aliases
.iter()
.map(|(name, value)| (name.clone(), value.clone()))
.collect();
aliases.sort_by(|left, right| left.0.cmp(&right.0));
aliases
}
fn set_alias(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.aliases.insert(name.into(), value.into());
}
fn remove_alias(&mut self, name: &str) -> Option<String> {
self.aliases.remove(name)
}
fn clear_aliases(&mut self) {
self.aliases.clear();
}
fn functions(&self) -> Vec<(String, AstCommand)> {
let mut functions: Vec<_> = self
.functions
.iter()
.map(|(name, body)| (name.clone(), body.clone()))
.collect();
functions.sort_by(|left, right| left.0.cmp(&right.0));
functions
}
fn set_function(&mut self, name: impl Into<String>, body: AstCommand) {
self.functions.insert(name.into(), body);
}
fn remove_function(&mut self, name: &str) -> Option<AstCommand> {
self.functions.remove(name)
}
fn clear_functions(&mut self) {
self.functions.clear();
}
fn inherited_fd_entries(&self) -> Vec<(i32, FileDescriptor)> {
let mut inherited: Vec<_> = self
.raw_fds
.iter()
.map(|(&child, &parent)| (child, parent))
.collect();
inherited.sort_by_key(|(child, _)| *child);
inherited
}
fn named_inherited_fd_entries(&self) -> Vec<NamedFileDescriptor> {
let mut inherited: Vec<_> = self
.named_fds
.iter()
.filter_map(|(name, &child)| {
self.raw_fds
.get(&child)
.copied()
.map(|parent| NamedFileDescriptor::new(name.clone(), child, parent))
})
.collect();
inherited.sort_by(|left, right| left.name.cmp(&right.name));
inherited
}
fn named_inherited_fd(&self, name: &str) -> Option<NamedFileDescriptor> {
let child_fd = *self.named_fds.get(name)?;
let parent_fd = *self.raw_fds.get(&child_fd)?;
Some(NamedFileDescriptor::new(name, child_fd, parent_fd))
}
fn set_inherited_fd(
&mut self,
child_fd: i32,
parent_fd: FileDescriptor,
) -> Option<FileDescriptor> {
self.raw_fds.insert(child_fd, parent_fd)
}
fn remove_inherited_fd(&mut self, child_fd: i32) -> Option<FileDescriptor> {
self.named_fds
.retain(|_, named_child| *named_child != child_fd);
self.raw_fds.remove(&child_fd)
}
fn set_named_inherited_fd(
&mut self,
named_fd: NamedFileDescriptor,
) -> Option<NamedFileDescriptor> {
let NamedFileDescriptor {
name,
child_fd,
parent_fd,
} = named_fd;
let previous = self.named_inherited_fd(&name);
self.named_fds.insert(name, child_fd);
let _ = self.set_inherited_fd(child_fd, parent_fd);
if let Some(ref previous) = previous
&& previous.child_fd != child_fd
&& !self
.named_fds
.values()
.any(|&named_child| named_child == previous.child_fd)
{
let _ = self.raw_fds.remove(&previous.child_fd);
}
previous
}
fn remove_named_inherited_fd(&mut self, name: &str) -> Option<NamedFileDescriptor> {
let child_fd = self.named_fds.remove(name)?;
let parent_fd = self.raw_fds.remove(&child_fd)?;
Some(NamedFileDescriptor::new(name, child_fd, parent_fd))
}
fn clear_named_inherited_fds(&mut self) {
let named_children = self
.named_fds
.drain()
.map(|(_, child)| child)
.collect::<Vec<_>>();
for child_fd in named_children {
let _ = self.raw_fds.remove(&child_fd);
}
}
fn clear_inherited_fds(&mut self) {
self.raw_fds.clear();
self.named_fds.clear();
}
}
#[derive(Clone)]
pub(crate) struct ShellDefinition {
pub(crate) identity: ShellIdentity,
pub(crate) startup_policy: StartupPolicy,
pub(crate) startup_sources: StartupSources,
pub(crate) security_policy: ShellSecurityPolicy,
pub(crate) startup_policy_explicit: bool,
pub(crate) option_schema: ShellOptionSchema,
pub(crate) language: ShellLanguage,
pub(crate) interactive_explicit: bool,
pub(crate) history_path: Option<PathBuf>,
pub(crate) history_appender: Option<Arc<HistoryAppender>>,
pub(crate) background_launcher: crate::policy::BackgroundLauncher,
pub(crate) command_policy: crate::policy::CommandPolicy,
pub(crate) builtin_registry: BuiltinRegistry,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PortableShellDefinition {
identity: ShellIdentity,
startup_policy: StartupPolicy,
startup_sources: StartupSources,
security_policy: ShellSecurityPolicy,
startup_policy_explicit: bool,
option_schema: ShellOptionSchema,
language: ShellLanguage,
interactive_explicit: bool,
history_path: Option<PathBuf>,
background_launcher: BackgroundLauncher,
command_policy: PortableCommandPolicy,
builtin_registry: PortableBuiltinRegistry,
}
impl Default for ShellDefinition {
fn default() -> Self {
Self {
identity: ShellIdentity::default(),
startup_policy: StartupPolicy::None,
startup_sources: StartupSources::default(),
security_policy: ShellSecurityPolicy::default(),
startup_policy_explicit: false,
option_schema: ShellOptionSchema::default(),
language: ShellLanguage::default(),
interactive_explicit: false,
history_path: None,
history_appender: None,
background_launcher: crate::policy::BackgroundLauncher::default(),
command_policy: crate::policy::CommandPolicy::default(),
builtin_registry: BuiltinRegistry::default(),
}
}
}
impl ShellDefinition {
pub(crate) fn portable_background_checkpoint(&self) -> PortableShellDefinition {
let (builtin_registry, non_portable_builtin_names) =
self.builtin_registry.portable_background_checkpoint();
PortableShellDefinition {
identity: self.identity.clone(),
startup_policy: self.startup_policy,
startup_sources: self.startup_sources.clone(),
security_policy: self.security_policy,
startup_policy_explicit: self.startup_policy_explicit,
option_schema: self.option_schema.clone(),
language: self.language,
interactive_explicit: self.interactive_explicit,
history_path: self.history_path.clone(),
background_launcher: self.background_launcher.clone(),
command_policy: self
.command_policy
.portable_background_checkpoint(non_portable_builtin_names),
builtin_registry,
}
}
}
impl PortableShellDefinition {
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(crate) fn into_shell_definition(self) -> ShellDefinition {
ShellDefinition {
identity: self.identity,
startup_policy: self.startup_policy,
startup_sources: self.startup_sources,
security_policy: self.security_policy,
startup_policy_explicit: self.startup_policy_explicit,
option_schema: self.option_schema,
language: self.language,
interactive_explicit: self.interactive_explicit,
history_path: self.history_path,
history_appender: None,
background_launcher: self.background_launcher,
command_policy: CommandPolicy::from_portable_background_checkpoint(self.command_policy),
builtin_registry: BuiltinRegistry::from_portable_background_checkpoint(
self.builtin_registry,
),
}
}
}
impl fmt::Debug for ShellDefinition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ShellDefinition")
.field("identity", &self.identity)
.field("startup_policy", &self.startup_policy)
.field("startup_sources", &self.startup_sources)
.field("security_policy", &self.security_policy)
.field("startup_policy_explicit", &self.startup_policy_explicit)
.field("option_schema", &self.option_schema)
.field("language", &self.language)
.field("interactive_explicit", &self.interactive_explicit)
.field("history_path", &self.history_path)
.field("has_history_appender", &self.history_appender.is_some())
.field("background_launcher", &self.background_launcher)
.field("command_policy", &self.command_policy)
.field("builtin_registry", &self.builtin_registry)
.finish()
}
}
fn build_session_state(
config: &BlueprintData,
definition: Arc<ShellDefinition>,
) -> shell::ShellState {
let mut state = shell::ShellState::new();
if config.inherit_env {
state.populate_env();
} else {
state.initialize_defaults();
}
state.variable_store.frame = vec![config.definition.identity.name().to_string()];
state.options = config.options.bits();
state.interactive = config.interactive;
state.manage_signals = config.manage_signals;
state.stdin_fd = config.stdio.stdin;
state.stdout_fd = config.stdio.stdout;
state.stderr_fd = config.stdio.stderr;
if let Some(cwd) = config.cwd.clone() {
state.path_state.cwd = cwd;
state.env_set_internal(
"PWD",
state.path_state.cwd.display().to_string(),
shell::VAR_EXPORT,
);
}
for (key, value, attrib) in &config.env {
let _ = state.env_set(key, value.clone(), attrib.bits());
}
state.variable_store.frame = config.seed.frame(config.definition.identity.name());
for (name, body) in config.seed.functions() {
state.set_function(name, body);
}
for (name, value) in config.seed.aliases() {
state.set_alias(name, value);
}
for (child_fd, parent_fd) in config.seed.inherited_fd_entries() {
let _ = state.set_inherited_fd(child_fd, parent_fd);
}
for named_fd in config.seed.named_inherited_fd_entries() {
let _ = state.set_named_inherited_fd(named_fd);
}
state.definition = definition;
state
}
#[derive(Clone)]
struct BlueprintData {
definition: ShellDefinition,
options: ShellOptions,
interactive: bool,
interactive_explicit: bool,
manage_signals: bool,
stdio: StdioConfig,
inherit_env: bool,
env: Vec<(String, String, VariableAttributes)>,
cwd: Option<PathBuf>,
seed: SessionSeed,
}
#[derive(Clone)]
pub struct ShellBlueprint {
blueprint: BlueprintData,
}
impl ShellBlueprint {
pub fn new() -> Self {
Self {
blueprint: BlueprintData::new(),
}
}
pub fn new_session(&self) -> SessionState {
let mut definition = self.blueprint.definition.clone();
definition.interactive_explicit = self.blueprint.interactive_explicit;
let definition = Arc::new(definition);
SessionState {
state: build_session_state(&self.blueprint, Arc::clone(&definition)),
definition,
}
}
}
impl Default for ShellBlueprint {
fn default() -> Self {
Self::new()
}
}
impl BlueprintData {
fn new() -> Self {
Self {
definition: ShellDefinition::default(),
options: ShellOptions::default(),
interactive: false,
interactive_explicit: false,
manage_signals: false,
stdio: StdioConfig::default(),
inherit_env: true,
env: Vec::new(),
cwd: None,
seed: SessionSeed::default(),
}
}
fn identity(mut self, identity: ShellIdentity) -> Self {
self.definition.identity = identity;
self
}
fn shell_name(mut self, name: impl Into<String>) -> Self {
self.definition.identity = ShellIdentity::named(name);
self
}
fn options(mut self, options: ShellOptions) -> Self {
self.options = options;
self
}
fn option_schema(mut self, option_schema: ShellOptionSchema) -> Self {
self.definition.option_schema = option_schema;
self
}
fn language(mut self, language: ShellLanguage) -> Self {
self.definition.language = language;
self
}
fn alias_expansion_enabled(mut self, enabled: bool) -> Self {
self.definition.language = self
.definition
.language
.with_alias_expansion_enabled(enabled);
self
}
fn function_definitions_enabled(mut self, enabled: bool) -> Self {
self.definition.language = self
.definition
.language
.with_function_definitions_enabled(enabled);
self
}
fn set_option_spec(mut self, option: ShellOptionSpec) -> Self {
self.definition.option_schema.set_option(option);
self
}
fn remove_option_spec(mut self, option: ShellOptions) -> Self {
self.definition.option_schema.remove_option(option);
self
}
fn interactive(mut self, interactive: bool) -> Self {
self.interactive = interactive;
self.interactive_explicit = true;
self
}
fn manage_signals(mut self, manage_signals: bool) -> Self {
self.manage_signals = manage_signals;
self
}
fn startup_policy(mut self, startup_policy: StartupPolicy) -> Self {
self.definition.startup_policy = startup_policy;
self.definition.startup_policy_explicit = true;
self
}
fn startup_sources(mut self, startup_sources: StartupSources) -> Self {
self.definition.startup_sources = startup_sources;
self
}
fn security_policy(mut self, security_policy: ShellSecurityPolicy) -> Self {
self.definition.security_policy = security_policy;
self
}
fn multi_tenant(mut self) -> Self {
self.definition.security_policy = ShellSecurityPolicy::multi_tenant();
self.definition.startup_policy = StartupPolicy::None;
self.definition.startup_policy_explicit = true;
self.definition.startup_sources = StartupSources::new()
.without_system_profile()
.without_user_profile()
.without_env_file_var();
self.options.remove(ShellOptions::MONITOR);
self.definition
.option_schema
.remove_option(ShellOptions::MONITOR);
self.manage_signals = false;
self.inherit_env = false;
self
}
fn system_profile_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.definition.startup_sources = self.definition.startup_sources.with_system_profile(path);
self
}
fn disable_system_profile(mut self) -> Self {
self.definition.startup_sources = self.definition.startup_sources.without_system_profile();
self
}
fn user_profile_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.definition.startup_sources = self.definition.startup_sources.with_user_profile(path);
self
}
fn disable_user_profile(mut self) -> Self {
self.definition.startup_sources = self.definition.startup_sources.without_user_profile();
self
}
fn startup_env_var(mut self, env_file_var: impl Into<String>) -> Self {
self.definition.startup_sources = self
.definition
.startup_sources
.with_env_file_var(env_file_var);
self
}
fn disable_startup_env(mut self) -> Self {
self.definition.startup_sources = self.definition.startup_sources.without_env_file_var();
self
}
fn stdio(mut self, stdio: StdioConfig) -> Self {
self.stdio = stdio;
self
}
fn history_path<P: Into<PathBuf>>(mut self, history_path: P) -> Self {
self.definition.history_path = Some(history_path.into());
self
}
fn history_appender<F>(mut self, history_appender: F) -> Self
where
F: Fn(&str) + Send + Sync + 'static,
{
self.definition.history_appender = Some(Arc::new(history_appender));
self
}
fn background_launcher(
mut self,
background_launcher: crate::policy::BackgroundLauncher,
) -> Self {
self.definition.background_launcher = background_launcher;
self
}
fn clear_builtins(mut self) -> Self {
self.definition.builtin_registry = BuiltinRegistry::empty();
self
}
fn clear_unspecified_utilities(mut self) -> Self {
self.definition.command_policy =
self.definition.command_policy.clear_unspecified_utilities();
self
}
fn add_unspecified_utility(mut self, name: impl Into<String>) -> Self {
self.definition.command_policy =
self.definition.command_policy.add_unspecified_utility(name);
self
}
fn remove_unspecified_utility(mut self, name: &str) -> Self {
self.definition.command_policy = self
.definition
.command_policy
.remove_unspecified_utility(name);
self
}
fn register_command_override(
mut self,
name: impl Into<String>,
command_override: crate::policy::CommandOverride,
) -> Self {
self.definition.command_policy = self
.definition
.command_policy
.register_override(name, command_override);
self
}
fn remove_command_override(mut self, name: &str) -> Self {
self.definition.command_policy = self.definition.command_policy.remove_override(name);
self
}
fn register_command_not_found_handler<F>(mut self, handler: F) -> Self
where
F: for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static,
{
self.definition.command_policy = self
.definition
.command_policy
.register_command_not_found_handler(handler);
self
}
fn remove_command_not_found_handler(mut self) -> Self {
self.definition.command_policy = self
.definition
.command_policy
.remove_command_not_found_handler();
self
}
fn clear_inherited_env(mut self) -> Self {
self.inherit_env = false;
self
}
fn frame<I, S>(mut self, frame: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.seed.set_frame(frame);
self
}
fn argv0(mut self, argv0: impl Into<String>) -> Self {
self.seed.set_argv0(argv0);
self
}
fn positional_parameters<I, S>(mut self, params: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.seed
.set_positional_parameters(params.into_iter().map(Into::into).collect());
self
}
fn env<K: Into<String>, V: Into<String>>(
mut self,
key: K,
value: V,
attrib: VariableAttributes,
) -> Self {
self.env.push((key.into(), value.into(), attrib));
self
}
fn current_dir<P: Into<PathBuf>>(mut self, cwd: P) -> Self {
self.cwd = Some(cwd.into());
self
}
fn alias(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.seed.set_alias(name, value);
self
}
fn remove_alias(mut self, name: &str) -> Self {
let _ = self.seed.remove_alias(name);
self
}
fn clear_aliases(mut self) -> Self {
self.seed.clear_aliases();
self
}
fn function(mut self, name: impl Into<String>, body: AstCommand) -> Self {
self.seed.set_function(name, body);
self
}
fn remove_function(mut self, name: &str) -> Self {
let _ = self.seed.remove_function(name);
self
}
fn clear_functions(mut self) -> Self {
self.seed.clear_functions();
self
}
fn inherited_fd(mut self, child_fd: i32, parent_fd: FileDescriptor) -> Self {
let _ = self.seed.set_inherited_fd(child_fd, parent_fd);
self
}
fn remove_inherited_fd(mut self, child_fd: i32) -> Self {
let _ = self.seed.remove_inherited_fd(child_fd);
self
}
fn clear_inherited_fds(mut self) -> Self {
self.seed.clear_inherited_fds();
self
}
fn named_inherited_fd(mut self, named_fd: NamedFileDescriptor) -> Self {
let _ = self.seed.set_named_inherited_fd(named_fd);
self
}
fn remove_named_inherited_fd(mut self, name: &str) -> Self {
let _ = self.seed.remove_named_inherited_fd(name);
self
}
fn clear_named_inherited_fds(mut self) -> Self {
self.seed.clear_named_inherited_fds();
self
}
fn remove_builtin(mut self, name: &str) -> Self {
let _ = self.definition.builtin_registry.remove(name);
self
}
fn register_builtin<F>(self, name: &str, builtin: F) -> Result<Self, BuildError>
where
F: for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static,
{
self.register_builtin_with_properties(name, BuiltinProperties::regular(), builtin)
}
fn register_special_builtin<F>(self, name: &str, builtin: F) -> Result<Self, BuildError>
where
F: for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static,
{
self.register_builtin_with_properties(name, BuiltinProperties::special(), builtin)
}
fn register_builtin_with_properties<F>(
mut self,
name: &str,
properties: BuiltinProperties,
builtin: F,
) -> Result<Self, BuildError>
where
F: for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static,
{
if self.definition.builtin_registry.contains(name) {
return Err(BuildError::BuiltinConflict(name.to_string()));
}
self.definition.builtin_registry.insert_custom(
name.to_string(),
properties,
Arc::new(builtin),
);
Ok(self)
}
}
impl Default for BlueprintData {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Default)]
pub struct ShellBuilder {
blueprint: BlueprintData,
build_error: Option<BuildError>,
}
impl ShellBuilder {
pub fn new() -> Self {
Self {
blueprint: BlueprintData::new(),
build_error: None,
}
}
pub fn from_blueprint(blueprint: ShellBlueprint) -> Self {
Self {
blueprint: blueprint.blueprint,
build_error: None,
}
}
fn update(mut self, update: impl FnOnce(BlueprintData) -> BlueprintData) -> Self {
self.blueprint = update(self.blueprint);
self
}
fn record(mut self, result: Result<BlueprintData, BuildError>) -> Self {
match result {
Ok(blueprint) => self.blueprint = blueprint,
Err(err) => {
if self.build_error.is_none() {
self.build_error = Some(err);
}
}
}
self
}
pub fn identity(self, identity: ShellIdentity) -> Self {
self.update(|blueprint| blueprint.identity(identity))
}
pub fn shell_name(self, name: impl Into<String>) -> Self {
self.update(|blueprint| blueprint.shell_name(name))
}
pub fn options(self, options: ShellOptions) -> Self {
self.update(|blueprint| blueprint.options(options))
}
pub fn option_schema(self, option_schema: ShellOptionSchema) -> Self {
self.update(|blueprint| blueprint.option_schema(option_schema))
}
pub fn set_option_spec(self, option: ShellOptionSpec) -> Self {
self.update(|blueprint| blueprint.set_option_spec(option))
}
pub fn remove_option_spec(self, option: ShellOptions) -> Self {
self.update(|blueprint| blueprint.remove_option_spec(option))
}
pub fn interactive(self, interactive: bool) -> Self {
self.update(|blueprint| blueprint.interactive(interactive))
}
pub fn manage_signals(self, manage_signals: bool) -> Self {
self.update(|blueprint| blueprint.manage_signals(manage_signals))
}
pub fn startup_policy(self, startup_policy: StartupPolicy) -> Self {
self.update(|blueprint| blueprint.startup_policy(startup_policy))
}
pub fn startup_sources(self, startup_sources: StartupSources) -> Self {
self.update(|blueprint| blueprint.startup_sources(startup_sources))
}
pub fn security_policy(self, security_policy: ShellSecurityPolicy) -> Self {
self.update(|blueprint| blueprint.security_policy(security_policy))
}
pub fn multi_tenant(self) -> Self {
self.update(BlueprintData::multi_tenant)
}
pub fn language(self, language: ShellLanguage) -> Self {
self.update(|blueprint| blueprint.language(language))
}
pub fn alias_expansion_enabled(self, enabled: bool) -> Self {
self.update(|blueprint| blueprint.alias_expansion_enabled(enabled))
}
pub fn function_definitions_enabled(self, enabled: bool) -> Self {
self.update(|blueprint| blueprint.function_definitions_enabled(enabled))
}
pub fn system_profile_path<P: Into<PathBuf>>(self, path: P) -> Self {
self.update(|blueprint| blueprint.system_profile_path(path))
}
pub fn disable_system_profile(self) -> Self {
self.update(BlueprintData::disable_system_profile)
}
pub fn user_profile_path<P: Into<PathBuf>>(self, path: P) -> Self {
self.update(|blueprint| blueprint.user_profile_path(path))
}
pub fn disable_user_profile(self) -> Self {
self.update(BlueprintData::disable_user_profile)
}
pub fn startup_env_var(self, env_file_var: impl Into<String>) -> Self {
self.update(|blueprint| blueprint.startup_env_var(env_file_var))
}
pub fn disable_startup_env(self) -> Self {
self.update(BlueprintData::disable_startup_env)
}
pub fn stdio(self, stdio: StdioConfig) -> Self {
self.update(|blueprint| blueprint.stdio(stdio))
}
pub fn history_path<P: Into<PathBuf>>(self, history_path: P) -> Self {
self.update(|blueprint| blueprint.history_path(history_path))
}
pub fn history_appender<F>(self, history_appender: F) -> Self
where
F: Fn(&str) + Send + Sync + 'static,
{
self.update(|blueprint| blueprint.history_appender(history_appender))
}
pub fn background_launcher(
self,
background_launcher: crate::policy::BackgroundLauncher,
) -> Self {
self.update(|blueprint| blueprint.background_launcher(background_launcher))
}
pub fn clear_builtins(self) -> Self {
self.update(BlueprintData::clear_builtins)
}
pub fn clear_unspecified_utilities(self) -> Self {
self.update(BlueprintData::clear_unspecified_utilities)
}
pub fn add_unspecified_utility(self, name: impl Into<String>) -> Self {
self.update(|blueprint| blueprint.add_unspecified_utility(name))
}
pub fn remove_unspecified_utility(self, name: &str) -> Self {
self.update(|blueprint| blueprint.remove_unspecified_utility(name))
}
pub fn register_command_override(
self,
name: impl Into<String>,
command_override: crate::policy::CommandOverride,
) -> Self {
self.update(|blueprint| blueprint.register_command_override(name, command_override))
}
pub fn remove_command_override(self, name: &str) -> Self {
self.update(|blueprint| blueprint.remove_command_override(name))
}
pub fn register_command_not_found_handler<F>(self, handler: F) -> Self
where
F: for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static,
{
self.update(|blueprint| blueprint.register_command_not_found_handler(handler))
}
pub fn remove_command_not_found_handler(self) -> Self {
self.update(BlueprintData::remove_command_not_found_handler)
}
pub fn clear_inherited_env(self) -> Self {
self.update(BlueprintData::clear_inherited_env)
}
pub fn frame<I, S>(self, frame: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.update(|blueprint| blueprint.frame(frame))
}
pub fn argv0(self, argv0: impl Into<String>) -> Self {
self.update(|blueprint| blueprint.argv0(argv0))
}
pub fn positional_parameters<I, S>(self, params: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.update(|blueprint| blueprint.positional_parameters(params))
}
pub fn env<K: Into<String>, V: Into<String>>(
self,
key: K,
value: V,
attrib: VariableAttributes,
) -> Self {
self.update(|blueprint| blueprint.env(key, value, attrib))
}
pub fn current_dir<P: Into<PathBuf>>(self, cwd: P) -> Self {
self.update(|blueprint| blueprint.current_dir(cwd))
}
pub fn alias(self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.update(|blueprint| blueprint.alias(name, value))
}
pub fn remove_alias(self, name: &str) -> Self {
self.update(|blueprint| blueprint.remove_alias(name))
}
pub fn clear_aliases(self) -> Self {
self.update(BlueprintData::clear_aliases)
}
pub fn function(self, name: impl Into<String>, body: AstCommand) -> Self {
self.update(|blueprint| blueprint.function(name, body))
}
pub fn remove_function(self, name: &str) -> Self {
self.update(|blueprint| blueprint.remove_function(name))
}
pub fn clear_functions(self) -> Self {
self.update(BlueprintData::clear_functions)
}
pub fn inherited_fd(self, child_fd: i32, parent_fd: FileDescriptor) -> Self {
self.update(|blueprint| blueprint.inherited_fd(child_fd, parent_fd))
}
pub fn remove_inherited_fd(self, child_fd: i32) -> Self {
self.update(|blueprint| blueprint.remove_inherited_fd(child_fd))
}
pub fn clear_inherited_fds(self) -> Self {
self.update(BlueprintData::clear_inherited_fds)
}
pub fn named_inherited_fd(self, named_fd: NamedFileDescriptor) -> Self {
self.update(|blueprint| blueprint.named_inherited_fd(named_fd))
}
pub fn remove_named_inherited_fd(self, name: &str) -> Self {
self.update(|blueprint| blueprint.remove_named_inherited_fd(name))
}
pub fn clear_named_inherited_fds(self) -> Self {
self.update(BlueprintData::clear_named_inherited_fds)
}
pub fn remove_builtin(self, name: &str) -> Self {
self.update(|blueprint| blueprint.remove_builtin(name))
}
pub fn register_builtin<F>(self, name: &str, builtin: F) -> Self
where
F: for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static,
{
let result = self.blueprint.clone().register_builtin(name, builtin);
self.record(result)
}
pub fn register_special_builtin<F>(self, name: &str, builtin: F) -> Self
where
F: for<'a> Fn(&mut BuiltinHost<'a>, &[String]) -> i32 + Send + Sync + 'static,
{
let result = self
.blueprint
.clone()
.register_special_builtin(name, builtin);
self.record(result)
}
pub fn blueprint(self) -> Result<ShellBlueprint, BuildError> {
if let Some(err) = self.build_error {
Err(err)
} else {
Ok(ShellBlueprint {
blueprint: self.blueprint,
})
}
}
pub fn new_session(self) -> Result<SessionState, BuildError> {
Ok(self.blueprint()?.new_session())
}
pub fn build<R: Runtime>(self, runtime: R) -> Result<Shell<R>, BuildError> {
Ok(Shell::with_blueprint(runtime, self.blueprint()?))
}
}
#[derive(Debug)]
pub struct Shell<R> {
runtime: R,
state: SessionState,
}
impl<R: Runtime> Shell<R> {
pub fn new(runtime: R) -> Self {
Self::with_blueprint(runtime, ShellBlueprint::new())
}
pub fn with_blueprint(runtime: R, blueprint: ShellBlueprint) -> Self {
Self {
runtime,
state: blueprint.new_session(),
}
}
pub fn identity(&self) -> &ShellIdentity {
self.state.identity()
}
pub fn shell_name(&self) -> &str {
self.state.shell_name()
}
pub fn env_get(&self, key: &str) -> Option<&str> {
self.state.env_get(key)
}
pub fn alias(&self, name: &str) -> Option<&str> {
self.state.alias(name)
}
pub fn function(&self, name: &str) -> Option<&AstCommand> {
self.state.function(name)
}
pub fn argv0(&self) -> Option<&str> {
self.state.argv0()
}
pub fn positional_parameters(&self) -> &[String] {
self.state.positional_parameters()
}
pub fn current_dir(&self) -> &Path {
self.state.current_dir()
}
pub fn options(&self) -> ShellOptions {
self.state.options()
}
pub fn has_option(&self, option: ShellOptions) -> bool {
self.state.has_option(option)
}
pub fn last_status(&self) -> i32 {
self.state.last_status()
}
pub fn exit_code(&self) -> Option<i32> {
self.state.exit_code()
}
pub fn last_background_pid(&self) -> Option<u32> {
self.state.last_background_pid()
}
pub fn run(&mut self, text: &str) -> RunOutcome {
self.state.run(&mut self.runtime, text)
}
pub fn run_program(&mut self, program: &Program) -> RunOutcome {
self.state.run_program(&mut self.runtime, program)
}
pub fn prepare<'ast>(&mut self, program: &'ast Program) -> PreparedProgram<'ast> {
let (runtime, state) = (&mut self.runtime, &mut self.state);
crate::advanced::Planner::new(state, runtime).prepare(program)
}
pub fn run_prepared(&mut self, program: &PreparedProgram<'_>) -> RunOutcome {
let (runtime, state) = (&mut self.runtime, &mut self.state);
crate::advanced::Planner::new(state, runtime).execute_plan(program)
}
#[cfg(feature = "frontend")]
pub fn run_cli(&mut self, argv: &[String]) -> RunOutcome {
crate::frontend::run_cli_with_shell(argv, self)
}
#[cfg(feature = "frontend")]
pub(crate) fn parts_mut(&mut self) -> (&mut SessionState, &mut R) {
(&mut self.state, &mut self.runtime)
}
#[cfg(feature = "frontend")]
pub fn run_interactive(&mut self) -> io::Result<RunOutcome> {
self.state.run_interactive(&mut self.runtime)
}
#[cfg(feature = "frontend")]
pub fn run_interactive_with_frontend<F: crate::frontend::InteractiveFrontend>(
&mut self,
frontend: &mut F,
) -> io::Result<RunOutcome> {
self.state
.run_interactive_with_frontend(&mut self.runtime, frontend)
}
}
#[cfg(feature = "frontend")]
pub(crate) fn interactive_buffer_needs_more_input(
buffer: &str,
language: &ShellLanguage,
aliases: Vec<(String, String)>,
) -> bool {
let mut parser = Parser::from_string(buffer);
configure_parser_for_language(&mut parser, language);
let aliases: HashMap<_, _> = aliases.into_iter().collect();
parser.set_alias_func(crate::parser::AliasFn::new(move |name| {
aliases.get(name).cloned()
}));
match parser.parse_program() {
Ok(_) => false,
Err(err) => err.range.end.offset >= buffer.len(),
}
}