use std::collections::BTreeSet;
use std::path::Path;
use std::sync::Arc;
use shuck_ast::{Name, NormalizedCommand};
use shuck_parser::ShellProfile;
use shuck_semantic::{FileContract, FileEntryContractCollector, FileEntryContractCollectorFactory};
use crate::ShellDialect;
mod contracts;
mod signals;
mod source_scan;
mod sourced_runtime;
#[cfg(test)]
mod tests;
mod zsh_caller_arrays;
pub use contracts::{
AmbientContractActivation, AmbientContractConfig, AmbientContractEffects, AmbientContractSpec,
AmbientFunctionContractSpec, EffectiveAmbientContracts, ResolvedAmbientContracts,
ResolvedAmbientRequestContracts,
};
pub(crate) struct AmbientContractCollector<'a> {
source: &'a str,
shell: ShellDialect,
signals: signals::AmbientSignals<'a>,
contracts: Arc<ResolvedAmbientContracts>,
completion_initializer_invoked: bool,
caller_scoped_array_length_names: BTreeSet<Name>,
}
pub(crate) struct AmbientContractCollectorFactory {
contracts: Arc<ResolvedAmbientContracts>,
}
impl FileEntryContractCollectorFactory for AmbientContractCollectorFactory {
fn collector_for_file<'a>(
&self,
source: &'a str,
path: Option<&'a Path>,
shell_profile: &ShellProfile,
) -> Option<Box<dyn FileEntryContractCollector + 'a>> {
Some(Box::new(AmbientContractCollector::new(
source,
path,
shell_dialect_from_profile(shell_profile),
Arc::clone(&self.contracts),
)))
}
}
impl<'a> AmbientContractCollector<'a> {
pub(crate) fn new(
source: &'a str,
path: Option<&'a Path>,
shell: ShellDialect,
contracts: Arc<ResolvedAmbientContracts>,
) -> Self {
let mut caller_scoped_array_length_names = BTreeSet::new();
if shell == ShellDialect::Zsh {
zsh_caller_arrays::collect_caller_scoped_array_length_names_from_source(
source,
&mut caller_scoped_array_length_names,
);
}
Self {
source,
shell,
signals: signals::AmbientSignals::new(source, path),
contracts,
completion_initializer_invoked: false,
caller_scoped_array_length_names,
}
}
fn file_entry_contract(&self) -> Option<FileContract> {
self.signals.path()?;
self.contracts.file_entry_contract(self, self.shell)
}
fn source_signals(&self) -> &signals::SourceSignals<'a> {
self.signals.source()
}
fn path_signals(&self) -> &signals::PathSignals {
self.signals
.path()
.expect("ambient contracts are only built for path-backed files")
}
}
impl AmbientContractCollectorFactory {
pub(crate) fn new(contracts: Arc<ResolvedAmbientContracts>) -> Self {
Self { contracts }
}
}
impl FileEntryContractCollector for AmbientContractCollector<'_> {
fn observe_simple_command(&mut self, command: &NormalizedCommand<'_>) {
self.completion_initializer_invoked |=
sourced_runtime::normalized_command_invokes_completion_initializer(
command,
self.source,
);
if self.shell == ShellDialect::Zsh {
for word in command.body_args() {
zsh_caller_arrays::collect_caller_scoped_array_length_names(
word,
&mut self.caller_scoped_array_length_names,
);
}
}
}
fn finish(&self) -> Option<FileContract> {
self.file_entry_contract()
}
}
fn shell_dialect_from_profile(profile: &ShellProfile) -> ShellDialect {
match profile.dialect {
shuck_parser::ShellDialect::Zsh => ShellDialect::Zsh,
shuck_parser::ShellDialect::Mksh => ShellDialect::Mksh,
shuck_parser::ShellDialect::Posix => ShellDialect::Sh,
shuck_parser::ShellDialect::Bash => ShellDialect::Bash,
}
}