Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nushell #1106

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -46,7 +46,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 @@ -186,6 +186,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 @@ -221,6 +231,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 @@ -244,7 +274,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 @@ -385,13 +414,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 @@ -404,6 +435,7 @@ pub enum Shell {
Bash(PathBuf),
#[cfg(unix)]
Tcsh(PathBuf),
Nushell(PathBuf),
#[cfg(windows)]
Powershell(PathBuf),
}
Expand All @@ -415,6 +447,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 @@ -427,6 +460,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 @@ -442,6 +476,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 @@ -477,7 +512,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 @@ -4,6 +4,8 @@

//! Handles the setup and initialization of runtime environments

mod config_nu;
mod env_nu;
#[cfg(unix)]
pub mod overlayfs;
#[cfg(unix)]
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 @@ -26,6 +26,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 @@ -379,6 +380,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 @@ -420,6 +424,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 @@ -440,6 +446,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 @@ -1035,6 +1043,16 @@ impl Runtime {
startup_csh::source(tmpdir_value_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 @@ -688,23 +688,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 @@ -715,6 +721,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