use std::io;
use std::path::Path;
use std::sync::Arc;
use crate::ast::{Command as AstCommand, Program, SimpleCommand};
#[cfg(feature = "frontend")]
use crate::embed::{HistoryAppender, interactive_buffer_needs_more_input};
use crate::embed::{NamedFileDescriptor, RunOutcome, StdioConfig, capture_outcome_for_state};
use crate::policy::{
ShellIdentity, ShellLanguage, ShellOptionSchema, ShellOptions, ShellSecurityPolicy,
StartupPolicy, StartupSources,
};
use crate::runtime::{Runtime, fd::FileDescriptor};
use crate::shell;
pub use crate::embed::ShellBlueprint;
pub use crate::plan::{
CommandResolutionError, DeferredExpansion, DeferredPipelineStageWork, DeferredReason,
ExecutionPlan as PreparedProgram, PipelineExecutionPlan as PreparedPipeline, PlannedAndOr,
PlannedCommandList, PlannedLazyAst, PlannedLazyNode, PlannedPipeline, PlannedPipelineStage,
PlannedProgram, PlannedSimpleCommand, PlannedSimpleCommandKind, PreparedExternalStagePlan,
};
#[derive(Debug)]
pub struct SessionState {
pub(crate) definition: Arc<crate::embed::ShellDefinition>,
pub(crate) state: shell::ShellState,
}
#[derive(Debug, Clone)]
pub struct DetachedSessionCheckpoint {
definition: Arc<crate::embed::ShellDefinition>,
state: shell::DetachedStateCheckpoint,
stdio: StdioConfig,
}
impl DetachedSessionCheckpoint {
pub fn new_session(&self) -> SessionState {
let definition = Arc::clone(&self.definition);
SessionState {
state: shell::ShellState::from_detached_session_checkpoint(
Arc::clone(&definition),
self.state.clone(),
self.stdio.stdin,
self.stdio.stdout,
self.stdio.stderr,
),
definition,
}
}
pub fn stdio(&self) -> StdioConfig {
self.stdio
}
}
impl Default for SessionState {
fn default() -> Self {
ShellBlueprint::default().new_session()
}
}
impl Drop for SessionState {
fn drop(&mut self) {
shell::discard_process_signal_state(&mut self.state);
}
}
impl SessionState {
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "frontend")]
pub(crate) fn state_mut(&mut self) -> &mut shell::ShellState {
&mut self.state
}
#[cfg(feature = "frontend")]
pub(crate) fn state(&self) -> &shell::ShellState {
&self.state
}
#[cfg(feature = "frontend")]
fn update_definition(&mut self, update: impl FnOnce(&mut crate::embed::ShellDefinition)) {
let definition = Arc::make_mut(&mut self.definition);
update(definition);
self.state.definition = Arc::clone(&self.definition);
}
pub fn identity(&self) -> &ShellIdentity {
&self.definition.identity
}
pub fn shell_name(&self) -> &str {
self.identity().name()
}
pub fn option_schema(&self) -> &ShellOptionSchema {
&self.definition.option_schema
}
pub fn startup_sources(&self) -> &StartupSources {
&self.definition.startup_sources
}
pub fn security_policy(&self) -> ShellSecurityPolicy {
self.definition.security_policy
}
pub fn language(&self) -> &ShellLanguage {
&self.definition.language
}
pub fn initialize<R: Runtime>(&mut self, runtime: &mut R) {
shell::initialize_shell_session(&mut self.state, runtime);
}
pub fn frame(&self) -> &[String] {
&self.state.variable_store.frame
}
pub fn run_program<R: Runtime>(&mut self, runtime: &mut R, program: &Program) -> RunOutcome {
let mut outcome = capture_outcome_for_state(&mut self.state, |state| {
let status = shell::run_program(state, runtime, program);
state.set_last_status(status);
status
});
self.finalize_outcome_if_exited(runtime, &mut outcome);
outcome
}
pub fn run<R: Runtime>(&mut self, runtime: &mut R, text: &str) -> RunOutcome {
let mut outcome = capture_outcome_for_state(&mut self.state, |state| {
let status = shell::run_string(state, runtime, text);
state.set_last_status(status);
status
});
self.finalize_outcome_if_exited(runtime, &mut outcome);
outcome
}
fn finalize_outcome_if_exited<R: Runtime>(
&mut self,
runtime: &mut R,
outcome: &mut RunOutcome,
) {
if self.state.exit_code < 0 {
return;
}
let final_status = shell::finalize_shell_state(&mut self.state, runtime, outcome.status);
self.state.set_last_status(final_status);
outcome.status = final_status;
outcome.exit_code = Some(final_status);
outcome.update_last_run_finished_status(final_status);
}
pub fn env_get(&self, key: &str) -> Option<&str> {
self.state.env_get(key)
}
pub fn exported_env(&self) -> Vec<(String, String)> {
self.state.exported_env()
}
pub fn aliases(&self) -> Vec<(String, String)> {
self.state.aliases()
}
pub fn alias(&self, name: &str) -> Option<&str> {
self.state.alias(name)
}
pub fn functions(&self) -> Vec<(String, AstCommand)> {
self.state.functions()
}
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 inherited_file_descriptors(&self) -> Vec<(i32, FileDescriptor)> {
self.state.inherited_fd_entries()
}
pub fn named_inherited_file_descriptors(&self) -> Vec<NamedFileDescriptor> {
self.state.named_inherited_fd_entries()
}
pub fn named_inherited_file_descriptor(&self, name: &str) -> Option<NamedFileDescriptor> {
self.state.named_inherited_fd(name)
}
pub fn detached_checkpoint(&self) -> DetachedSessionCheckpoint {
DetachedSessionCheckpoint {
definition: Arc::clone(&self.definition),
state: self.state.detached_session_checkpoint(),
stdio: self.stdio(),
}
}
pub fn current_dir(&self) -> &Path {
&self.state.path_state.cwd
}
pub fn options(&self) -> ShellOptions {
ShellOptions::from_bits(self.state.options)
}
pub fn has_option(&self, option: ShellOptions) -> bool {
(self.state.options & option.bits()) != 0
}
pub fn interactive(&self) -> bool {
self.state.interactive
}
pub fn manage_signals(&self) -> bool {
self.state.manage_signals
}
pub fn stdio(&self) -> StdioConfig {
StdioConfig {
stdin: self.state.stdin_fd,
stdout: self.state.stdout_fd,
stderr: self.state.stderr_fd,
}
}
pub fn last_status(&self) -> i32 {
self.state.last_status
}
pub fn exit_code(&self) -> Option<i32> {
(self.state.exit_code >= 0).then_some(self.state.exit_code)
}
pub fn last_background_pid(&self) -> Option<u32> {
self.state.last_background_pid()
}
pub fn startup_policy(&self) -> StartupPolicy {
self.definition.startup_policy
}
#[cfg(feature = "frontend")]
pub(crate) fn has_explicit_startup_policy(&self) -> bool {
self.definition.startup_policy_explicit
}
#[cfg(feature = "frontend")]
pub(crate) fn warn_vi_unsupported_once(&mut self) {
shell::maybe_warn_vi_unsupported(&mut self.state);
}
#[cfg(feature = "frontend")]
pub fn run_interactive<R: Runtime>(&mut self, runtime: &mut R) -> io::Result<RunOutcome> {
let mut frontend = crate::frontend::FdFrontend;
self.run_interactive_with_frontend(runtime, &mut frontend)
}
#[cfg(feature = "frontend")]
pub fn run_interactive_with_frontend<R: Runtime, F: crate::frontend::InteractiveFrontend>(
&mut self,
runtime: &mut R,
frontend: &mut F,
) -> io::Result<RunOutcome> {
frontend.on_unsupported_vi_mode(self)?;
let mut pending = String::new();
loop {
if pending.is_empty() && self.has_option(ShellOptions::NOTIFY) {
shell::maybe_notify_jobs(&mut self.state, runtime);
}
let prompt = frontend.prompt(self, !pending.is_empty())?;
let input = frontend.read_line(self, &prompt)?;
let Some(input) = input else {
if pending.is_empty() && self.has_option(ShellOptions::IGNOREEOF) {
self.write_stderr("Use \"exit\" to leave the shell.")?;
continue;
}
if !pending.is_empty() {
let result = self.run(runtime, &pending);
return Ok(result);
}
let status = self.state.last_status;
return Ok(RunOutcome::empty_from_state(status, &self.state));
};
if !pending.is_empty() {
pending.push('\n');
}
pending.push_str(&input);
if interactive_buffer_needs_more_input(&pending, self.language(), self.aliases()) {
continue;
}
frontend.append_history(self, &pending)?;
let result = self.run(runtime, &pending);
pending.clear();
if result.exit_code.is_some() {
return Ok(result);
}
}
}
#[cfg(feature = "frontend")]
pub(crate) fn set_startup_policy(&mut self, startup_policy: StartupPolicy) {
self.update_definition(|definition| {
definition.startup_policy = startup_policy;
definition.startup_policy_explicit = true;
});
}
#[cfg(feature = "frontend")]
pub(crate) fn set_history_path<P: Into<std::path::PathBuf>>(
&mut self,
history_path: Option<P>,
) {
let history_path = history_path.map(Into::into);
self.update_definition(|definition| definition.history_path = history_path);
}
pub fn history_path(&self) -> Option<&Path> {
self.definition.history_path.as_deref()
}
#[cfg(feature = "frontend")]
pub(crate) fn history_appender(&self) -> Option<&Arc<HistoryAppender>> {
self.definition.history_appender.as_ref()
}
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)
}
}
pub struct Planner<'session, 'runtime, R> {
session: &'session mut SessionState,
runtime: &'runtime mut R,
}
impl<'session, 'runtime, R: Runtime> Planner<'session, 'runtime, R> {
pub fn new(session: &'session mut SessionState, runtime: &'runtime mut R) -> Self {
Self { session, runtime }
}
pub fn prepare_simple_command<'ast>(
&mut self,
command: &'ast SimpleCommand,
) -> Result<PlannedSimpleCommand<'ast>, i32> {
shell::plan_simple_command(&mut self.session.state, self.runtime, command)
}
pub fn prepare_pipeline<'ast>(
&mut self,
pipeline: &'ast crate::ast::Pipeline,
) -> PreparedPipeline<'ast> {
crate::plan::PipelineExecutionPlan {
pipeline,
inner: shell::build_pipeline_execution(&mut self.session.state, self.runtime, pipeline),
}
}
pub fn prepare<'ast>(&mut self, program: &'ast Program) -> PreparedProgram<'ast> {
crate::plan::ExecutionPlan {
program,
inner: shell::build_program_execution(&mut self.session.state, self.runtime, program),
}
}
pub fn execute_pipeline(&mut self, plan: &PreparedPipeline<'_>) -> RunOutcome {
let mut outcome = capture_outcome_for_state(&mut self.session.state, |state| {
let status = shell::execute_pipeline_plan(state, self.runtime, &plan.inner);
state.set_last_status(status);
status
});
self.session
.finalize_outcome_if_exited(self.runtime, &mut outcome);
outcome
}
pub fn execute_plan(&mut self, plan: &PreparedProgram<'_>) -> RunOutcome {
let mut outcome = capture_outcome_for_state(&mut self.session.state, |state| {
let status = shell::run_planned_program(
state,
self.runtime,
plan.program,
&plan.inner,
None,
None,
);
state.set_last_status(status);
status
});
self.session
.finalize_outcome_if_exited(self.runtime, &mut outcome);
outcome
}
}