Skip to content
Draft
15 changes: 13 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ bytes = "1.5"
cached = "0.48.1"
chrono = { version = "0.4.34", features = ["serde"] }
clap = { version = "4.5", features = ["derive", "env"] }
clap_complete = "4.3"
clap_complete = "4.5"
colored = "2.0.0"
config = "0.14.0"
console = "0.15.8"
Expand Down
44 changes: 42 additions & 2 deletions crates/spfs/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ pub fn build_interactive_shell_command(
],
vars: vec![shell_message],
}),
Shell::Nushell(nu) => Ok(Command {
executable: nu.into(),
args: vec![
"--env-config".into(),
rt.config.nu_env_file.as_os_str().to_owned(),
"--config".into(),
rt.config.nu_config_file.as_os_str().to_owned(),
],
vars: vec![shell_message],
}),
#[cfg(windows)]
Shell::Powershell(ps1) => Ok(Command {
executable: ps1.into(),
Expand Down Expand Up @@ -222,6 +232,26 @@ where
let startup_file = match shell.kind() {
ShellKind::Bash => &runtime.config.sh_startup_file,
ShellKind::Tcsh => &runtime.config.csh_startup_file,
ShellKind::Nushell => {
let mut cmd = command.into();
for arg in args.into_iter().map(Into::into) {
cmd.push(" ");
cmd.push(arg);
}
let args = vec![
"--env-config".into(),
runtime.config.nu_env_file.as_os_str().to_owned(),
"--config".into(),
runtime.config.nu_config_file.as_os_str().to_owned(),
"-c".into(),
cmd,
];
return Ok(Command {
executable: shell.executable().into(),
args,
vars: vec![],
});
}
ShellKind::Powershell => {
let mut cmd = command.into();
for arg in args.into_iter().map(Into::into) {
Expand All @@ -245,7 +275,6 @@ where

let mut shell_args = vec![startup_file.into(), command.into()];
shell_args.extend(args.into_iter().map(Into::into));

Ok(Command {
executable: shell.executable().into(),
args: shell_args,
Expand Down Expand Up @@ -395,13 +424,15 @@ pub enum ShellKind {
Bash,
Tcsh,
Powershell,
Nushell,
}

impl AsRef<str> for ShellKind {
fn as_ref(&self) -> &str {
match self {
Self::Bash => "bash",
Self::Tcsh => "tcsh",
Self::Nushell => "nu",
Self::Powershell => "powershell.exe",
}
}
Expand All @@ -414,6 +445,7 @@ pub enum Shell {
Bash(PathBuf),
#[cfg(unix)]
Tcsh(PathBuf),
Nushell(PathBuf),
#[cfg(windows)]
Powershell(PathBuf),
}
Expand All @@ -425,6 +457,7 @@ impl Shell {
Self::Bash(_) => ShellKind::Bash,
#[cfg(unix)]
Self::Tcsh(_) => ShellKind::Tcsh,
Self::Nushell(_) => ShellKind::Nushell,
#[cfg(windows)]
Self::Powershell(_) => ShellKind::Powershell,
}
Expand All @@ -437,6 +470,7 @@ impl Shell {
Self::Bash(p) => p,
#[cfg(unix)]
Self::Tcsh(p) => p,
Self::Nushell(p) => p,
#[cfg(windows)]
Self::Powershell(p) => p,
}
Expand All @@ -452,6 +486,7 @@ impl Shell {
Some(n) if n == ShellKind::Bash.as_ref() => Ok(Self::Bash(path.to_owned())),
#[cfg(unix)]
Some(n) if n == ShellKind::Tcsh.as_ref() => Ok(Self::Tcsh(path.to_owned())),
Some(n) if n == ShellKind::Nushell.as_ref() => Ok(Self::Nushell(path.to_owned())),
#[cfg(windows)]
Some(n) if n == ShellKind::Powershell.as_ref() => Ok(Self::Powershell(path.to_owned())),
Some(_) => Err(Error::new(format!("Unsupported shell: {path:?}"))),
Expand Down Expand Up @@ -487,7 +522,12 @@ impl Shell {
}
}

for kind in &[ShellKind::Bash, ShellKind::Tcsh, ShellKind::Powershell] {
for kind in &[
ShellKind::Bash,
ShellKind::Tcsh,
ShellKind::Powershell,
ShellKind::Nushell,
] {
if let Some(path) = which(kind) {
if let Ok(shell) = Shell::from_path(path) {
return Ok(shell);
Expand Down
17 changes: 17 additions & 0 deletions crates/spfs/src/runtime/config_nu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Contributors to the SPK project.
// SPDX-License-Identifier: Apache-2.0
// https://github.com/spkenv/spk
// Warning Nushell version >=0.97

pub fn source<T>(_tmpdir: Option<&T>) -> String
where
T: AsRef<str>,
{
r#"
$env.config = {
show_banner: false,
}
$env.SPFS_SHELL_MESSAGE? | print
"#
.to_string()
}
78 changes: 78 additions & 0 deletions crates/spfs/src/runtime/env_nu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Contributors to the SPK project.
// SPDX-License-Identifier: Apache-2.0
// https://github.com/spkenv/spk
// Warning Nushell version >=0.97

pub fn source<T>(_tmpdir: Option<&T>) -> String
where
T: AsRef<str>,
{
r#"
def create_left_prompt [] {
let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) {
null => $env.PWD
'' => '~'
$relative_pwd => ([~ $relative_pwd] | path join)
}

let path_color = (if (is-admin) { ansi red_bold } else { ansi green_bold })
let separator_color = (if (is-admin) { ansi light_red_bold } else { ansi light_green_bold })
let path_segment = $"($path_color)($dir)(ansi reset)"

$path_segment | str replace --all (char path_sep) $"($separator_color)(char path_sep)($path_color)"
}

def create_right_prompt [] {
# create a right prompt in magenta with green separators and am/pm underlined
let time_segment = ([
(ansi reset)
(ansi magenta)
(date now | format date '%x %X') # try to respect user's locale
] | str join | str replace --regex --all "([/:])" $"(ansi green)${1}(ansi magenta)" |
str replace --regex --all "([AP]M)" $"(ansi magenta_underline)${1}")

let last_exit_code = if ($env.LAST_EXIT_CODE != 0) {([
(ansi rb)
($env.LAST_EXIT_CODE)
] | str join)
} else { "" }

([$last_exit_code, (char space), $time_segment] | str join)
}

# Use nushell functions to define your right and left prompt
$env.PROMPT_COMMAND = {|| create_left_prompt }
# FIXME: This default is not implemented in rust code as of 2023-09-08.
$env.PROMPT_COMMAND_RIGHT = {|| create_right_prompt }

# The prompt indicators are environmental variables that represent
# the state of the prompt
$env.PROMPT_INDICATOR = {|| "> " }
$env.PROMPT_INDICATOR_VI_INSERT = {|| ": " }
$env.PROMPT_INDICATOR_VI_NORMAL = {|| "> " }
$env.PROMPT_MULTILINE_INDICATOR = {|| "::: " }


$env.ENV_CONVERSIONS = {
"PATH": {
from_string: { |s| $s | split row (char esep) | path expand --no-symlink }
to_string: { |v| $v | path expand --no-symlink | str join (char esep) }
}
"Path": {
from_string: { |s| $s | split row (char esep) | path expand --no-symlink }
to_string: { |v| $v | path expand --no-symlink | str join (char esep) }
}
}

let $spfs_startup_dir = if $nu.os-info.name == "windows" {
"C:/spfs/etc/spfs/startup.d"
} else if $nu.os-info.name == "linux" {
"/spfs/etc/spfs/startup.d"
} else {
exit 1
}

$env.NU_VENDOR_AUTOLOAD_DIR = ($spfs_startup_dir)
"#
.to_string()
}
2 changes: 2 additions & 0 deletions crates/spfs/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
//! Handles the setup and initialization of runtime environments

pub mod live_layer;
mod config_nu;
mod env_nu;
#[cfg(unix)]
pub mod overlayfs;
pub mod spec_api_version;
Expand Down
18 changes: 18 additions & 0 deletions crates/spfs/src/runtime/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use tokio::io::AsyncReadExt;

#[cfg(windows)]
use super::startup_ps;
use super::{config_nu, env_nu};
#[cfg(unix)]
use super::{startup_csh, startup_sh};
use crate::encoding::Digest;
Expand Down Expand Up @@ -148,6 +149,9 @@ pub struct Config {
pub sh_startup_file: PathBuf,
/// The location of the startup script for csh-based shells
pub csh_startup_file: PathBuf,
/// The location of the startup script for nushell-based shells
pub nu_env_file: PathBuf,
pub nu_config_file: PathBuf,
/// The location of the expect utility script used for csh-based shell environments
/// \[DEPRECATED\] This field still exists for spk/spfs interop but is unused
#[serde(skip_deserializing, default = "Config::default_csh_expect_file")]
Expand Down Expand Up @@ -189,6 +193,8 @@ impl Config {
const SH_STARTUP_FILE: &'static str = "startup.sh";
const CSH_STARTUP_FILE: &'static str = ".cshrc";
const PS_STARTUP_FILE: &'static str = "startup.ps1";
const NU_ENV_FILE: &'static str = "env.nu";
const NU_CONFIG_FILE: &'static str = "config.nu";
const DEV_NULL: &'static str = "/dev/null";

/// Return a dummy value for the legacy csh_expect_file field.
Expand All @@ -209,6 +215,8 @@ impl Config {
csh_startup_file: root.join(Self::CSH_STARTUP_FILE),
csh_expect_file: Self::default_csh_expect_file(),
ps_startup_file: temp_dir().join(Self::PS_STARTUP_FILE),
nu_env_file: root.join(Self::NU_ENV_FILE),
nu_config_file: root.join(Self::NU_CONFIG_FILE),
runtime_dir: Some(root),
tmpfs_size,
mount_namespace: None,
Expand Down Expand Up @@ -811,6 +819,16 @@ impl Runtime {
startup_csh::source(environment_overrides_for_child_process),
)
.map_err(|err| Error::RuntimeWriteError(self.config.csh_startup_file.clone(), err))?;
std::fs::write(
&self.config.nu_env_file,
env_nu::source(tmpdir_value_for_child_process),
)
.map_err(|err| Error::RuntimeWriteError(self.config.nu_env_file.clone(), err))?;
std::fs::write(
&self.config.nu_config_file,
config_nu::source(tmpdir_value_for_child_process),
)
.map_err(|err| Error::RuntimeWriteError(self.config.nu_config_file.clone(), err))?;
#[cfg(windows)]
std::fs::write(
&self.config.ps_startup_file,
Expand Down
11 changes: 10 additions & 1 deletion crates/spk-build/src/build/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,23 +689,29 @@ where

let mut startup_file_csh = startup_dir.join(format!("spk_{}.csh", package.name()));
let mut startup_file_sh = startup_dir.join(format!("spk_{}.sh", package.name()));
let mut startup_file_nu = startup_dir.join(format!("spk_{}.nu", package.name()));
let mut csh_file = std::fs::File::create(&startup_file_csh)
.map_err(|err| Error::FileOpenError(startup_file_csh.to_owned(), err))?;
let mut sh_file = std::fs::File::create(&startup_file_sh)
.map_err(|err| Error::FileOpenError(startup_file_sh.to_owned(), err))?;

let mut nu_file = std::fs::File::create(&startup_file_nu)
.map_err(|err| Error::FileOpenError(startup_file_nu.to_owned(), err))?;
for op in ops {
if let Some(priority) = op.priority() {
let original_startup_file_sh_name = startup_file_sh.clone();
let original_startup_file_csh_name = startup_file_csh.clone();
let original_startup_file_nu_name = startup_file_nu.clone();

startup_file_sh.set_file_name(format!("{priority:02}_spk_{}.sh", package.name()));
startup_file_csh.set_file_name(format!("{priority:02}_spk_{}.csh", package.name()));
startup_file_nu.set_file_name(format!("{priority:02}_spk_{}.nu", package.name()));

std::fs::rename(original_startup_file_sh_name, &startup_file_sh)
.map_err(|err| Error::FileWriteError(startup_file_sh.to_owned(), err))?;
std::fs::rename(original_startup_file_csh_name, &startup_file_csh)
.map_err(|err| Error::FileWriteError(startup_file_csh.to_owned(), err))?;
std::fs::rename(original_startup_file_nu_name, &startup_file_nu)
.map_err(|err| Error::FileWriteError(startup_file_nu.to_owned(), err))?;

continue;
}
Expand All @@ -716,6 +722,9 @@ where
sh_file
.write_fmt(format_args!("{}\n", op.bash_source()))
.map_err(|err| Error::FileWriteError(startup_file_sh.to_owned(), err))?;
nu_file
.write_fmt(format_args!("{}\n", op.nushell_source()))
.map_err(|err| Error::FileWriteError(startup_file_nu.to_owned(), err))?;
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions crates/spk-cli/group1/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ spk-storage = { workspace = true }
strip-ansi-escapes = { version = "0.1.1" }
tokio = { workspace = true, features = ["rt"] }
tracing = { workspace = true }
clap_complete_nushell ={ version = "4.5"}

[dev-dependencies]
rstest = { workspace = true }
Expand Down
Loading
Loading