Start to Add WASM Support Again (#14418)

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
The [nushell/demo](https://github.com/nushell/demo) project successfully
demonstrated running Nushell in the browser using WASM. However, the
current version of Nushell cannot be easily built for the
`wasm32-unknown-unknown` target, the default for `wasm-bindgen`.

This PR introduces initial support for the `wasm32-unknown-unknown`
target by disabling OS-dependent features such as filesystem access, IO,
and platform/system-specific functionality. This separation is achieved
using a new `os` feature in the following crates:

 - `nu-cmd-lang`
 - `nu-command`
 - `nu-engine`
 - `nu-protocol`

The `os` feature includes all functionality that interacts with an
operating system. It is enabled by default, but can be disabled using
`--no-default-features`. All crates that depend on these core crates now
use `--no-default-features` to allow compilation for WASM.

To demonstrate compatibility, the following script builds all crates
expected to work with WASM. Direct user interaction, running external
commands, working with plugins, and features requiring `openssl` are out
of scope for now due to their complexity or reliance on C libraries,
which are difficult to compile and link in a WASM environment.

```nushell
[ # compatible crates
	"nu-cmd-base",
	"nu-cmd-extra",
	"nu-cmd-lang",
	"nu-color-config",
	"nu-command",
	"nu-derive-value",
	"nu-engine",
	"nu-glob",
	"nu-json",
	"nu-parser",
	"nu-path",
	"nu-pretty-hex",
	"nu-protocol",
	"nu-std",
	"nu-system",
	"nu-table",
	"nu-term-grid",
	"nu-utils",
	"nuon"
] | each {cargo build -p $in --target wasm32-unknown-unknown --no-default-features}
```

## Caveats
This PR has a few caveats:
1. **`miette` and `terminal-size` Dependency Issue**
`miette` depends on `terminal-size`, which uses `rustix` when the target
is not Windows. However, `rustix` requires `std::os::unix`, which is
unavailable in WASM. To address this, I opened a
[PR](https://github.com/eminence/terminal-size/pull/68) for
`terminal-size` to conditionally compile `rustix` only when the target
is Unix. For now, the `Cargo.toml` includes patches to:
    - Use my forked version of `terminal-size`.
- ~~Use an unreleased version of `miette` that depends on
`terminal-size@0.4`.~~

These patches are temporary and can be removed once the upstream changes
are merged and released.

2. **Test Output Adjustments**
Due to the slight bump in the `miette` version, one test required
adjustments to accommodate minor formatting changes in the error output,
such as shifted newlines.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
This shouldn't break anything but allows using some crates for targeting
`wasm32-unknown-unknown` to revive the demo page eventually.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

I did not add any extra tests, I just checked that compiling works, also
when using the host target but unselecting the `os` feature.

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
~~Breaking the wasm support can be easily done by adding some `use`s or
by adding a new dependency, we should definitely add some CI that also
at least builds against wasm to make sure that building for it keep
working.~~
I added a job to build wasm.

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
This commit is contained in:
Piepmatz 2024-11-30 14:57:11 +01:00 committed by GitHub
parent 07a37f9b47
commit 3d5f853b03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 490 additions and 234 deletions

View File

@ -162,3 +162,34 @@ jobs:
else else
echo "no changes in working directory"; echo "no changes in working directory";
fi fi
build-wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
- name: Add wasm32-unknown-unknown target
run: rustup target add wasm32-unknown-unknown
- run: cargo build -p nu-cmd-base --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-cmd-extra --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-cmd-lang --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-color-config --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-command --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-derive-value --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-engine --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-glob --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-json --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-parser --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-path --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-pretty-hex --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-protocol --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-std --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-system --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-table --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-term-grid --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nu-utils --no-default-features --target wasm32-unknown-unknown
- run: cargo build -p nuon --no-default-features --target wasm32-unknown-unknown

10
Cargo.lock generated
View File

@ -2647,7 +2647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.52.6", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -3368,6 +3368,7 @@ dependencies = [
"fancy-regex", "fancy-regex",
"filesize", "filesize",
"filetime", "filetime",
"getrandom",
"human-date-parser", "human-date-parser",
"indexmap", "indexmap",
"indicatif", "indicatif",
@ -3755,6 +3756,7 @@ dependencies = [
name = "nu-utils" name = "nu-utils"
version = "0.100.1" version = "0.100.1"
dependencies = [ dependencies = [
"crossterm 0.28.1",
"crossterm_winapi", "crossterm_winapi",
"fancy-regex", "fancy-regex",
"log", "log",
@ -6847,9 +6849,9 @@ dependencies = [
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.4.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
dependencies = [ dependencies = [
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@ -7874,7 +7876,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]

View File

@ -134,6 +134,7 @@ quickcheck = "1.0"
quickcheck_macros = "1.0" quickcheck_macros = "1.0"
quote = "1.0" quote = "1.0"
rand = "0.8" rand = "0.8"
getrandom = "0.2" # pick same version that rand requires
rand_chacha = "0.3.1" rand_chacha = "0.3.1"
ratatui = "0.26" ratatui = "0.26"
rayon = "1.10" rayon = "1.10"

View File

@ -19,11 +19,11 @@ tempfile = { workspace = true }
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" }
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", features = ["os"] }
nu-path = { path = "../nu-path", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" }
nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.100.1", optional = true } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.100.1", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["os"] }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1" }
nu-color-config = { path = "../nu-color-config", version = "0.100.1" } nu-color-config = { path = "../nu-color-config", version = "0.100.1" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }

View File

@ -13,10 +13,10 @@ version = "0.100.1"
workspace = true workspace = true
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" }
nu-path = { path = "../nu-path", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
indexmap = { workspace = true } indexmap = { workspace = true }
miette = { workspace = true } miette = { workspace = true }

View File

@ -17,12 +17,12 @@ workspace = true
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" }
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-json = { version = "0.100.1", path = "../nu-json" } nu-json = { version = "0.100.1", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" }
nu-pretty-hex = { version = "0.100.1", path = "../nu-pretty-hex" } nu-pretty-hex = { version = "0.100.1", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
# Potential dependencies for extras # Potential dependencies for extras
heck = { workspace = true } heck = { workspace = true }

View File

@ -15,10 +15,10 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
itertools = { workspace = true } itertools = { workspace = true }
shadow-rs = { version = "0.36", default-features = false } shadow-rs = { version = "0.36", default-features = false }
@ -27,6 +27,17 @@ shadow-rs = { version = "0.36", default-features = false }
shadow-rs = { version = "0.36", default-features = false } shadow-rs = { version = "0.36", default-features = false }
[features] [features]
default = ["os"]
os = [
"nu-engine/os",
"nu-protocol/os",
"nu-utils/os",
]
plugin = [
"nu-protocol/plugin",
"os",
]
mimalloc = [] mimalloc = []
trash-support = [] trash-support = []
sqlite = [] sqlite = []

View File

@ -169,6 +169,7 @@ fn run(
let origin = match stream.source() { let origin = match stream.source() {
ByteStreamSource::Read(_) => "unknown", ByteStreamSource::Read(_) => "unknown",
ByteStreamSource::File(_) => "file", ByteStreamSource::File(_) => "file",
#[cfg(feature = "os")]
ByteStreamSource::Child(_) => "external", ByteStreamSource::Child(_) => "external",
}; };

View File

@ -1,9 +1,8 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env}; use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env};
use nu_protocol::{ #[cfg(feature = "os")]
engine::Closure, use nu_protocol::process::{ChildPipe, ChildProcess};
process::{ChildPipe, ChildProcess}, use nu_protocol::{engine::Closure, ByteStream, ByteStreamSource, OutDest};
ByteStream, ByteStreamSource, OutDest,
};
use std::{ use std::{
io::{Cursor, Read}, io::{Cursor, Read},
thread, thread,
@ -119,6 +118,13 @@ impl Command for Do {
match result { match result {
Ok(PipelineData::ByteStream(stream, metadata)) if capture_errors => { Ok(PipelineData::ByteStream(stream, metadata)) if capture_errors => {
let span = stream.span(); let span = stream.span();
#[cfg(not(feature = "os"))]
return Err(ShellError::DisabledOsSupport {
msg: "Cannot create a thread to receive stdout message.".to_string(),
span: Some(span),
});
#[cfg(feature = "os")]
match stream.into_child() { match stream.into_child() {
Ok(mut child) => { Ok(mut child) => {
// Use a thread to receive stdout message. // Use a thread to receive stdout message.
@ -196,6 +202,7 @@ impl Command for Do {
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value
) => ) =>
{ {
#[cfg(feature = "os")]
if let ByteStreamSource::Child(child) = stream.source_mut() { if let ByteStreamSource::Child(child) = stream.source_mut() {
child.ignore_error(true); child.ignore_error(true);
} }

View File

@ -35,6 +35,7 @@ impl Command for Ignore {
mut input: PipelineData, mut input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
if let PipelineData::ByteStream(stream, _) = &mut input { if let PipelineData::ByteStream(stream, _) = &mut input {
#[cfg(feature = "os")]
if let ByteStreamSource::Child(child) = stream.source_mut() { if let ByteStreamSource::Child(child) = stream.source_mut() {
child.ignore_error(true); child.ignore_error(true);
} }

View File

@ -116,6 +116,11 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
Value::string(features_enabled().join(", "), span), Value::string(features_enabled().join(", "), span),
); );
#[cfg(not(feature = "plugin"))]
let _ = engine_state;
#[cfg(feature = "plugin")]
{
// Get a list of plugin names and versions if present // Get a list of plugin names and versions if present
let installed_plugins = engine_state let installed_plugins = engine_state
.plugins() .plugins()
@ -134,6 +139,7 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
"installed_plugins", "installed_plugins",
Value::string(installed_plugins.join(", "), span), Value::string(installed_plugins.join(", "), span),
); );
}
Ok(Value::record(record, span).into_pipeline_data()) Ok(Value::record(record, span).into_pipeline_data())
} }

View File

@ -1,3 +1,4 @@
#![cfg_attr(not(feature = "os"), allow(unused))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
mod core_commands; mod core_commands;
mod default_context; mod default_context;

View File

@ -14,8 +14,8 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-json = { path = "../nu-json", version = "0.100.1" } nu-json = { path = "../nu-json", version = "0.100.1" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }

View File

@ -18,17 +18,17 @@ workspace = true
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" }
nu-color-config = { path = "../nu-color-config", version = "0.100.1" } nu-color-config = { path = "../nu-color-config", version = "0.100.1" }
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.100.1" } nu-glob = { path = "../nu-glob", version = "0.100.1" }
nu-json = { path = "../nu-json", version = "0.100.1" } nu-json = { path = "../nu-json", version = "0.100.1" }
nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" }
nu-path = { path = "../nu-path", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.100.1" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.100.1" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-system = { path = "../nu-system", version = "0.100.1" } nu-system = { path = "../nu-system", version = "0.100.1" }
nu-table = { path = "../nu-table", version = "0.100.1" } nu-table = { path = "../nu-table", version = "0.100.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.100.1" } nu-term-grid = { path = "../nu-term-grid", version = "0.100.1" }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.100.1" } nuon = { path = "../nuon", version = "0.100.1" }
@ -43,7 +43,7 @@ chardetng = { workspace = true }
chrono = { workspace = true, features = ["std", "unstable-locales", "clock"], default-features = false } chrono = { workspace = true, features = ["std", "unstable-locales", "clock"], default-features = false }
chrono-humanize = { workspace = true } chrono-humanize = { workspace = true }
chrono-tz = { workspace = true } chrono-tz = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true, optional = true }
csv = { workspace = true } csv = { workspace = true }
dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] } dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] }
digest = { workspace = true, default-features = false } digest = { workspace = true, default-features = false }
@ -61,19 +61,20 @@ lscolors = { workspace = true, default-features = false, features = ["nu-ansi-te
md5 = { workspace = true } md5 = { workspace = true }
mime = { workspace = true } mime = { workspace = true }
mime_guess = { workspace = true } mime_guess = { workspace = true }
multipart-rs = { workspace = true } multipart-rs = { workspace = true, optional = true }
native-tls = { workspace = true } native-tls = { workspace = true, optional = true }
notify-debouncer-full = { workspace = true, default-features = false } notify-debouncer-full = { workspace = true, default-features = false, optional = true }
num-format = { workspace = true } num-format = { workspace = true }
num-traits = { workspace = true } num-traits = { workspace = true }
oem_cp = { workspace = true } oem_cp = { workspace = true }
open = { workspace = true } open = { workspace = true, optional = true }
os_pipe = { workspace = true } os_pipe = { workspace = true, optional = true }
pathdiff = { workspace = true } pathdiff = { workspace = true }
percent-encoding = { workspace = true } percent-encoding = { workspace = true }
print-positions = { workspace = true } print-positions = { workspace = true }
quick-xml = { workspace = true } quick-xml = { workspace = true }
rand = { workspace = true } rand = { workspace = true, optional = true }
getrandom = { workspace = true, optional = true }
rayon = { workspace = true } rayon = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
roxmltree = { workspace = true } roxmltree = { workspace = true }
@ -89,26 +90,26 @@ tabled = { workspace = true, features = ["ansi"], default-features = false }
titlecase = { workspace = true } titlecase = { workspace = true }
toml = { workspace = true, features = ["preserve_order"] } toml = { workspace = true, features = ["preserve_order"] }
unicode-segmentation = { workspace = true } unicode-segmentation = { workspace = true }
ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"] } ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json"] }
url = { workspace = true } url = { workspace = true }
uu_cp = { workspace = true } uu_cp = { workspace = true, optional = true }
uu_mkdir = { workspace = true } uu_mkdir = { workspace = true, optional = true }
uu_mktemp = { workspace = true } uu_mktemp = { workspace = true, optional = true }
uu_mv = { workspace = true } uu_mv = { workspace = true, optional = true }
uu_touch = { workspace = true } uu_touch = { workspace = true, optional = true }
uu_uname = { workspace = true } uu_uname = { workspace = true, optional = true }
uu_whoami = { workspace = true } uu_whoami = { workspace = true, optional = true }
uuid = { workspace = true, features = ["v4"] } uuid = { workspace = true, features = ["v4"], optional = true }
v_htmlescape = { workspace = true } v_htmlescape = { workspace = true }
wax = { workspace = true } wax = { workspace = true }
which = { workspace = true } which = { workspace = true, optional = true }
unicode-width = { workspace = true } unicode-width = { workspace = true }
data-encoding = { version = "2.6.0", features = ["alloc"] } data-encoding = { version = "2.6.0", features = ["alloc"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = { workspace = true } winreg = { workspace = true }
[target.'cfg(not(windows))'.dependencies] [target.'cfg(all(not(windows), not(target_arch = "wasm32")))'.dependencies]
uucore = { workspace = true, features = ["mode"] } uucore = { workspace = true, features = ["mode"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
@ -134,7 +135,53 @@ features = [
workspace = true workspace = true
[features] [features]
plugin = ["nu-parser/plugin"] default = ["os"]
os = [
# include other features
"js",
"network",
"nu-protocol/os",
"nu-utils/os",
# os-dependant dependencies
"crossterm",
"notify-debouncer-full",
"open",
"os_pipe",
"uu_cp",
"uu_mkdir",
"uu_mktemp",
"uu_mv",
"uu_touch",
"uu_uname",
"uu_whoami",
"which",
]
# The dependencies listed below need 'getrandom'.
# They work with JS (usually with wasm-bindgen) or regular OS support.
# Hence they are also put under the 'os' feature to avoid repetition.
js = [
"getrandom",
"getrandom/js",
"rand",
"uuid",
]
# These dependencies require networking capabilities, especially the http
# interface requires openssl which is not easy to embed into wasm,
# using rustls could solve this issue.
network = [
"multipart-rs",
"native-tls",
"ureq/native-tls",
"uuid",
]
plugin = [
"nu-parser/plugin",
"os",
]
sqlite = ["rusqlite"] sqlite = ["rusqlite"]
trash-support = ["trash"] trash-support = ["trash"]

View File

@ -177,4 +177,9 @@ fn get_thread_id() -> u64 {
{ {
nix::sys::pthread::pthread_self() as u64 nix::sys::pthread::pthread_self() as u64
} }
#[cfg(target_arch = "wasm32")]
{
// wasm doesn't have any threads accessible, so we return 0 as a fallback
0
}
} }

View File

@ -1,6 +1,6 @@
use super::inspect_table; use super::inspect_table;
use crossterm::terminal::size;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_utils::terminal_size;
#[derive(Clone)] #[derive(Clone)]
pub struct Inspect; pub struct Inspect;
@ -38,7 +38,7 @@ impl Command for Inspect {
let original_input = input_val.clone(); let original_input = input_val.clone();
let description = input_val.get_type().to_string(); let description = input_val.get_type().to_string();
let (cols, _rows) = size().unwrap_or((0, 0)); let (cols, _rows) = terminal_size().unwrap_or((0, 0));
let table = inspect_table::build_table(input_val, description, cols as usize); let table = inspect_table::build_table(input_val, description, cols as usize);

View File

@ -27,6 +27,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
} }
// Filters // Filters
#[cfg(feature = "rand")]
bind_command! {
Shuffle
}
bind_command! { bind_command! {
All, All,
Any, Any,
@ -72,7 +76,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Rename, Rename,
Reverse, Reverse,
Select, Select,
Shuffle,
Skip, Skip,
SkipUntil, SkipUntil,
SkipWhile, SkipWhile,
@ -114,6 +117,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
}; };
// System // System
#[cfg(feature = "os")]
bind_command! { bind_command! {
Complete, Complete,
External, External,
@ -161,10 +165,12 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
ViewSpan, ViewSpan,
}; };
#[cfg(windows)] #[cfg(all(feature = "os", windows))]
bind_command! { RegistryQuery } bind_command! { RegistryQuery }
#[cfg(any( #[cfg(all(
feature = "os",
any(
target_os = "android", target_os = "android",
target_os = "linux", target_os = "linux",
target_os = "freebsd", target_os = "freebsd",
@ -172,6 +178,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
target_os = "openbsd", target_os = "openbsd",
target_os = "macos", target_os = "macos",
target_os = "windows" target_os = "windows"
)
))] ))]
bind_command! { Ps }; bind_command! { Ps };
@ -219,6 +226,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
}; };
// FileSystem // FileSystem
#[cfg(feature = "os")]
bind_command! { bind_command! {
Cd, Cd,
Ls, Ls,
@ -237,6 +245,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
}; };
// Platform // Platform
#[cfg(feature = "os")]
bind_command! { bind_command! {
Ansi, Ansi,
AnsiLink, AnsiLink,
@ -255,7 +264,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Whoami, Whoami,
}; };
#[cfg(unix)] #[cfg(all(unix, feature = "os"))]
bind_command! { ULimit }; bind_command! { ULimit };
// Date // Date
@ -380,6 +389,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
} }
// Network // Network
#[cfg(feature = "network")]
bind_command! { bind_command! {
Http, Http,
HttpDelete, HttpDelete,
@ -389,6 +399,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
HttpPost, HttpPost,
HttpPut, HttpPut,
HttpOptions, HttpOptions,
Port,
}
bind_command! {
Url, Url,
UrlBuildQuery, UrlBuildQuery,
UrlSplitQuery, UrlSplitQuery,
@ -396,10 +409,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
UrlEncode, UrlEncode,
UrlJoin, UrlJoin,
UrlParse, UrlParse,
Port,
} }
// Random // Random
#[cfg(feature = "rand")]
bind_command! { bind_command! {
Random, Random,
RandomBool, RandomBool,

View File

@ -1,4 +1,6 @@
use nu_engine::{command_prelude::*, get_full_help}; use nu_cmd_base::util::get_editor;
use nu_engine::{command_prelude::*, env_to_strings, get_full_help};
use nu_system::ForegroundChild;
#[derive(Clone)] #[derive(Clone)]
pub struct ConfigMeta; pub struct ConfigMeta;
@ -36,3 +38,79 @@ impl Command for ConfigMeta {
vec!["options", "setup"] vec!["options", "setup"]
} }
} }
#[cfg(not(feature = "os"))]
pub(super) fn start_editor(
_: &'static str,
_: &EngineState,
_: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
Err(ShellError::DisabledOsSupport {
msg: "Running external commands is not available without OS support.".to_string(),
span: Some(call.head),
})
}
#[cfg(feature = "os")]
pub(super) fn start_editor(
config_path: &'static str,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
// Find the editor executable.
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
let editor_executable =
crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(ShellError::ExternalCommand {
label: format!("`{editor_name}` not found"),
help: "Failed to find the editor executable".into(),
span: call.head,
})?;
let Some(config_path) = engine_state.get_config_path(config_path) else {
return Err(ShellError::GenericError {
error: format!("Could not find $nu.{config_path}"),
msg: format!("Could not find $nu.{config_path}"),
span: None,
help: None,
inner: vec![],
});
};
let config_path = config_path.to_string_lossy().to_string();
// Create the command.
let mut command = std::process::Command::new(editor_executable);
// Configure PWD.
command.current_dir(cwd);
// Configure environment variables.
let envs = env_to_strings(engine_state, stack)?;
command.env_clear();
command.envs(envs);
// Configure args.
command.arg(config_path);
command.args(editor_args);
// Spawn the child process. On Unix, also put the child process to
// foreground if we're in an interactive session.
#[cfg(windows)]
let child = ForegroundChild::spawn(command)?;
#[cfg(unix)]
let child = ForegroundChild::spawn(
command,
engine_state.is_interactive,
&engine_state.pipeline_externals_state,
)?;
// Wrap the output into a `PipelineData::ByteStream`.
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,
))
}

View File

@ -1,7 +1,4 @@
use nu_cmd_base::util::get_editor; use nu_engine::command_prelude::*;
use nu_engine::{command_prelude::*, env_to_strings};
use nu_protocol::{process::ChildProcess, ByteStream};
use nu_system::ForegroundChild;
#[derive(Clone)] #[derive(Clone)]
pub struct ConfigEnv; pub struct ConfigEnv;
@ -81,60 +78,6 @@ impl Command for ConfigEnv {
return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data()); return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data());
} }
// Find the editor executable. super::config_::start_editor("env-path", engine_state, stack, call)
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(
ShellError::ExternalCommand {
label: format!("`{editor_name}` not found"),
help: "Failed to find the editor executable".into(),
span: call.head,
},
)?;
let Some(env_path) = engine_state.get_config_path("env-path") else {
return Err(ShellError::GenericError {
error: "Could not find $nu.env-path".into(),
msg: "Could not find $nu.env-path".into(),
span: None,
help: None,
inner: vec![],
});
};
let env_path = env_path.to_string_lossy().to_string();
// Create the command.
let mut command = std::process::Command::new(editor_executable);
// Configure PWD.
command.current_dir(cwd);
// Configure environment variables.
let envs = env_to_strings(engine_state, stack)?;
command.env_clear();
command.envs(envs);
// Configure args.
command.arg(env_path);
command.args(editor_args);
// Spawn the child process. On Unix, also put the child process to
// foreground if we're in an interactive session.
#[cfg(windows)]
let child = ForegroundChild::spawn(command)?;
#[cfg(unix)]
let child = ForegroundChild::spawn(
command,
engine_state.is_interactive,
&engine_state.pipeline_externals_state,
)?;
// Wrap the output into a `PipelineData::ByteStream`.
let child = ChildProcess::new(child, None, false, call.head)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,
))
} }
} }

View File

@ -1,7 +1,4 @@
use nu_cmd_base::util::get_editor; use nu_engine::command_prelude::*;
use nu_engine::{command_prelude::*, env_to_strings};
use nu_protocol::{process::ChildProcess, ByteStream};
use nu_system::ForegroundChild;
#[derive(Clone)] #[derive(Clone)]
pub struct ConfigNu; pub struct ConfigNu;
@ -83,60 +80,6 @@ impl Command for ConfigNu {
return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data()); return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data());
} }
// Find the editor executable. super::config_::start_editor("config-path", engine_state, stack, call)
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(
ShellError::ExternalCommand {
label: format!("`{editor_name}` not found"),
help: "Failed to find the editor executable".into(),
span: call.head,
},
)?;
let Some(config_path) = engine_state.get_config_path("config-path") else {
return Err(ShellError::GenericError {
error: "Could not find $nu.config-path".into(),
msg: "Could not find $nu.config-path".into(),
span: None,
help: None,
inner: vec![],
});
};
let config_path = config_path.to_string_lossy().to_string();
// Create the command.
let mut command = std::process::Command::new(editor_executable);
// Configure PWD.
command.current_dir(cwd);
// Configure environment variables.
let envs = env_to_strings(engine_state, stack)?;
command.env_clear();
command.envs(envs);
// Configure args.
command.arg(config_path);
command.args(editor_args);
// Spawn the child process. On Unix, also put the child process to
// foreground if we're in an interactive session.
#[cfg(windows)]
let child = ForegroundChild::spawn(command)?;
#[cfg(unix)]
let child = ForegroundChild::spawn(
command,
engine_state.is_interactive,
&engine_state.pipeline_externals_state,
)?;
// Wrap the output into a `PipelineData::ByteStream`.
let child = ChildProcess::new(child, None, false, call.head)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,
))
} }
} }

View File

@ -103,3 +103,9 @@ fn is_root_impl() -> bool {
elevated elevated
} }
#[cfg(target_arch = "wasm32")]
fn is_root_impl() -> bool {
// in wasm we don't have a user system, so technically we are never root
false
}

View File

@ -102,6 +102,7 @@ impl Command for Save {
ByteStreamSource::File(source) => { ByteStreamSource::File(source) => {
stream_to_file(source, size, signals, file, span, progress)?; stream_to_file(source, size, signals, file, span, progress)?;
} }
#[cfg(feature = "os")]
ByteStreamSource::Child(mut child) => { ByteStreamSource::Child(mut child) => {
fn write_or_consume_stderr( fn write_or_consume_stderr(
stderr: ChildPipe, stderr: ChildPipe,

View File

@ -37,6 +37,7 @@ mod reject;
mod rename; mod rename;
mod reverse; mod reverse;
mod select; mod select;
#[cfg(feature = "rand")]
mod shuffle; mod shuffle;
mod skip; mod skip;
mod sort; mod sort;
@ -95,6 +96,7 @@ pub use reject::Reject;
pub use rename::Rename; pub use rename::Rename;
pub use reverse::Reverse; pub use reverse::Reverse;
pub use select::Select; pub use select::Select;
#[cfg(feature = "rand")]
pub use shuffle::Shuffle; pub use shuffle::Shuffle;
pub use skip::*; pub use skip::*;
pub use sort::Sort; pub use sort::Sort;

View File

@ -1,7 +1,9 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
#[cfg(feature = "os")]
use nu_protocol::process::ChildPipe;
use nu_protocol::{ use nu_protocol::{
byte_stream::copy_with_signals, engine::Closure, process::ChildPipe, report_shell_error, byte_stream::copy_with_signals, engine::Closure, report_shell_error, ByteStream,
ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals, ByteStreamSource, OutDest, PipelineMetadata, Signals,
}; };
use std::{ use std::{
io::{self, Read, Write}, io::{self, Read, Write},
@ -152,6 +154,7 @@ use it in your pipeline."#
metadata, metadata,
)) ))
} }
#[cfg(feature = "os")]
ByteStreamSource::Child(mut child) => { ByteStreamSource::Child(mut child) => {
let stderr_thread = if use_stderr { let stderr_thread = if use_stderr {
let stderr_thread = if let Some(stderr) = child.stderr.take() { let stderr_thread = if let Some(stderr) = child.stderr.take() {
@ -454,6 +457,7 @@ fn copy(src: impl Read, dest: impl Write, info: &StreamInfo) -> Result<(), Shell
Ok(()) Ok(())
} }
#[cfg(feature = "os")]
fn copy_pipe(pipe: ChildPipe, dest: impl Write, info: &StreamInfo) -> Result<(), ShellError> { fn copy_pipe(pipe: ChildPipe, dest: impl Write, info: &StreamInfo) -> Result<(), ShellError> {
match pipe { match pipe {
ChildPipe::Pipe(pipe) => copy(pipe, dest, info), ChildPipe::Pipe(pipe) => copy(pipe, dest, info),
@ -477,6 +481,7 @@ fn copy_on_thread(
.map_err(|e| e.into_spanned(span).into()) .map_err(|e| e.into_spanned(span).into())
} }
#[cfg(feature = "os")]
fn copy_pipe_on_thread( fn copy_pipe_on_thread(
pipe: ChildPipe, pipe: ChildPipe,
dest: impl Write + Send + 'static, dest: impl Write + Send + 'static,

View File

@ -1,3 +1,4 @@
#![cfg_attr(not(feature = "os"), allow(unused))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
mod bytes; mod bytes;
mod charting; mod charting;
@ -8,6 +9,7 @@ mod default_context;
mod env; mod env;
mod example_test; mod example_test;
mod experimental; mod experimental;
#[cfg(feature = "os")]
mod filesystem; mod filesystem;
mod filters; mod filters;
mod formats; mod formats;
@ -18,8 +20,10 @@ mod math;
mod misc; mod misc;
mod network; mod network;
mod path; mod path;
#[cfg(feature = "os")]
mod platform; mod platform;
mod progress_bar; mod progress_bar;
#[cfg(feature = "rand")]
mod random; mod random;
mod removed; mod removed;
mod shells; mod shells;
@ -27,6 +31,7 @@ mod sort_utils;
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
mod stor; mod stor;
mod strings; mod strings;
#[cfg(feature = "os")]
mod system; mod system;
mod viewers; mod viewers;
@ -40,6 +45,7 @@ pub use env::*;
#[cfg(test)] #[cfg(test)]
pub use example_test::{test_examples, test_examples_with_commands}; pub use example_test::{test_examples, test_examples_with_commands};
pub use experimental::*; pub use experimental::*;
#[cfg(feature = "os")]
pub use filesystem::*; pub use filesystem::*;
pub use filters::*; pub use filters::*;
pub use formats::*; pub use formats::*;
@ -50,7 +56,9 @@ pub use math::*;
pub use misc::*; pub use misc::*;
pub use network::*; pub use network::*;
pub use path::*; pub use path::*;
#[cfg(feature = "os")]
pub use platform::*; pub use platform::*;
#[cfg(feature = "rand")]
pub use random::*; pub use random::*;
pub use removed::*; pub use removed::*;
pub use shells::*; pub use shells::*;
@ -58,6 +66,7 @@ pub use sort_utils::*;
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
pub use stor::*; pub use stor::*;
pub use strings::*; pub use strings::*;
#[cfg(feature = "os")]
pub use system::*; pub use system::*;
pub use viewers::*; pub use viewers::*;

View File

@ -1,8 +1,12 @@
#[cfg(feature = "network")]
mod http; mod http;
#[cfg(feature = "network")]
mod port; mod port;
mod url; mod url;
#[cfg(feature = "network")]
pub use self::http::*; pub use self::http::*;
pub use self::url::*; pub use self::url::*;
#[cfg(feature = "network")]
pub use port::SubCommand as Port; pub use port::SubCommand as Port;

View File

@ -1,11 +1,10 @@
// use super::icons::{icon_for_file, iconify_style_ansi_to_nu}; // use super::icons::{icon_for_file, iconify_style_ansi_to_nu};
use super::icons::icon_for_file; use super::icons::icon_for_file;
use crossterm::terminal::size;
use lscolors::Style; use lscolors::Style;
use nu_engine::{command_prelude::*, env_to_string}; use nu_engine::{command_prelude::*, env_to_string};
use nu_protocol::Config; use nu_protocol::Config;
use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions};
use nu_utils::get_ls_colors; use nu_utils::{get_ls_colors, terminal_size};
use std::path::Path; use std::path::Path;
#[derive(Clone)] #[derive(Clone)]
@ -192,7 +191,7 @@ fn create_grid_output(
let cols = if let Some(col) = width_param { let cols = if let Some(col) = width_param {
col as u16 col as u16
} else if let Ok((w, _h)) = size() { } else if let Ok((w, _h)) = terminal_size() {
w w
} else { } else {
80u16 80u16

View File

@ -2,7 +2,6 @@
// overall reduce the redundant calls to StyleComputer etc. // overall reduce the redundant calls to StyleComputer etc.
// the goal is to configure it once... // the goal is to configure it once...
use crossterm::terminal::size;
use lscolors::{LsColors, Style}; use lscolors::{LsColors, Style};
use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; use nu_color_config::{color_from_hex, StyleComputer, TextStyle};
use nu_engine::{command_prelude::*, env_to_string}; use nu_engine::{command_prelude::*, env_to_string};
@ -15,7 +14,7 @@ use nu_table::{
common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell,
StringResult, TableOpts, TableOutput, StringResult, TableOpts, TableOutput,
}; };
use nu_utils::get_ls_colors; use nu_utils::{get_ls_colors, terminal_size};
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
io::{IsTerminal, Read}, io::{IsTerminal, Read},
@ -30,7 +29,7 @@ const STREAM_PAGE_SIZE: usize = 1000;
fn get_width_param(width_param: Option<i64>) -> usize { fn get_width_param(width_param: Option<i64>) -> usize {
if let Some(col) = width_param { if let Some(col) = width_param {
col as usize col as usize
} else if let Ok((w, _h)) = size() { } else if let Ok((w, _h)) = terminal_size() {
w as usize w as usize
} else { } else {
80 80
@ -712,6 +711,13 @@ fn make_clickable_link(
) -> String { ) -> String {
// uri's based on this https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda // uri's based on this https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
#[cfg(any(
unix,
windows,
target_os = "redox",
target_os = "wasi",
target_os = "hermit"
))]
if show_clickable_links { if show_clickable_links {
format!( format!(
"\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\", "\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\",
@ -727,6 +733,18 @@ fn make_clickable_link(
None => full_path, None => full_path,
} }
} }
#[cfg(not(any(
unix,
windows,
target_os = "redox",
target_os = "wasi",
target_os = "hermit"
)))]
match link_name {
Some(link_name) => link_name.to_string(),
None => full_path,
}
} }
struct PagingTableCreator { struct PagingTableCreator {

View File

@ -14,12 +14,20 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-path = { path = "../nu-path", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" }
nu-glob = { path = "../nu-glob", version = "0.100.1" } nu-glob = { path = "../nu-glob", version = "0.100.1" }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
log = { workspace = true } log = { workspace = true }
terminal_size = { workspace = true } terminal_size = { workspace = true }
[features] [features]
plugin = [] default = ["os"]
os = [
"nu-protocol/os",
"nu-utils/os",
]
plugin = [
"nu-protocol/plugin",
"os",
]

View File

@ -8,9 +8,9 @@ use nu_protocol::{
Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack, StateWorkingSet, Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack, StateWorkingSet,
}, },
ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode}, ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
ByteStreamSource, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest,
OutDest, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, Signals,
Signals, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
}; };
use nu_utils::IgnoreCaseExt; use nu_utils::IgnoreCaseExt;
@ -478,8 +478,9 @@ fn eval_instruction<D: DebugContext>(
Ok(Continue) Ok(Continue)
} }
Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) { Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) {
#[cfg(feature = "os")]
PipelineData::ByteStream(stream, _) PipelineData::ByteStream(stream, _)
if matches!(stream.source(), ByteStreamSource::Child(_)) => if matches!(stream.source(), nu_protocol::ByteStreamSource::Child(_)) =>
{ {
Ok(Continue) Ok(Continue)
} }
@ -513,7 +514,7 @@ fn eval_instruction<D: DebugContext>(
span: Some(*span), span: Some(*span),
})?; })?;
let is_external = if let PipelineData::ByteStream(stream, ..) = &src { let is_external = if let PipelineData::ByteStream(stream, ..) = &src {
matches!(stream.source(), ByteStreamSource::Child(..)) stream.source().is_external()
} else { } else {
false false
}; };

View File

@ -15,11 +15,11 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-path = { path = "../nu-path", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.100.1" } nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.100.1" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
bytesize = { workspace = true } bytesize = { workspace = true }
chrono = { default-features = false, features = ['std'], workspace = true } chrono = { default-features = false, features = ['std'], workspace = true }

View File

@ -60,6 +60,7 @@ fn expand_tilde_with_home(path: impl AsRef<Path>, home: Option<PathBuf>) -> Path
} }
} }
#[cfg(not(target_arch = "wasm32"))]
fn fallback_home_dir(username: &str) -> PathBuf { fn fallback_home_dir(username: &str) -> PathBuf {
PathBuf::from_iter([FALLBACK_USER_HOME_BASE_DIR, username]) PathBuf::from_iter([FALLBACK_USER_HOME_BASE_DIR, username])
} }
@ -110,6 +111,13 @@ fn user_home_dir(username: &str) -> PathBuf {
} }
} }
#[cfg(target_arch = "wasm32")]
fn user_home_dir(_: &str) -> PathBuf {
// if WASI is used, we try to get a home dir via HOME env, otherwise we don't have a home dir
let home = std::env::var("HOME").unwrap_or_else(|_| "/".to_string());
PathBuf::from(home)
}
/// Returns true if the shell is running inside the Termux terminal emulator /// Returns true if the shell is running inside the Termux terminal emulator
/// app. /// app.
#[cfg(target_os = "android")] #[cfg(target_os = "android")]

View File

@ -36,6 +36,13 @@ pub fn has_trailing_slash(path: &Path) -> bool {
last == Some(&b'/') last == Some(&b'/')
} }
/// `true` if the path has a trailing slash, including if it's the root directory.
#[cfg(target_arch = "wasm32")]
pub fn has_trailing_slash(path: &Path) -> bool {
// in the web paths are often just URLs, they are separated by forward slashes
path.to_str().map_or(false, |s| s.ends_with('/'))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -14,8 +14,8 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", features = ["plugin"] }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.100.1" } nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.100.1" }
nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-features = false } nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1" }

View File

@ -16,7 +16,7 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
nu-path = { path = "../nu-path", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" }
nu-system = { path = "../nu-system", version = "0.100.1" } nu-system = { path = "../nu-system", version = "0.100.1" }
nu-derive-value = { path = "../nu-derive-value", version = "0.100.1" } nu-derive-value = { path = "../nu-derive-value", version = "0.100.1" }
@ -31,14 +31,14 @@ fancy-regex = { workspace = true }
heck = { workspace = true } heck = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
lru = { workspace = true } lru = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace"] } miette = { workspace = true, features = ["fancy-no-backtrace"]}
num-format = { workspace = true } num-format = { workspace = true }
rmp-serde = { workspace = true, optional = true } rmp-serde = { workspace = true, optional = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
thiserror = "2.0" thiserror = "2.0"
typetag = "0.2" typetag = "0.2"
os_pipe = { workspace = true, features = ["io_safety"] } os_pipe = { workspace = true, optional = true, features = ["io_safety"] }
log = { workspace = true } log = { workspace = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
@ -49,8 +49,15 @@ dirs-sys = { workspace = true }
windows-sys = { workspace = true } windows-sys = { workspace = true }
[features] [features]
default = ["os"]
os = [
"nu-utils/os",
"os_pipe",
]
plugin = [ plugin = [
"brotli", "brotli",
"os",
"rmp-serde", "rmp-serde",
] ]

View File

@ -1458,6 +1458,17 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"#
#[label = "while running this code"] #[label = "while running this code"]
span: Option<Span>, span: Option<Span>,
}, },
#[error("OS feature is disabled: {msg}")]
#[diagnostic(
code(nu::shell::os_disabled),
help("You're probably running outside an OS like a browser, we cannot support this")
)]
DisabledOsSupport {
msg: String,
#[label = "while running this code"]
span: Option<Span>,
},
} }
impl ShellError { impl ShellError {

View File

@ -1,3 +1,4 @@
#![cfg_attr(not(feature = "os"), allow(unused))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
mod alias; mod alias;
pub mod ast; pub mod ast;
@ -17,6 +18,7 @@ pub mod parser_path;
mod pipeline; mod pipeline;
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
mod plugin; mod plugin;
#[cfg(feature = "os")]
pub mod process; pub mod process;
mod signature; mod signature;
pub mod span; pub mod span;

View File

@ -1,8 +1,7 @@
//! Module managing the streaming of raw bytes between pipeline elements //! Module managing the streaming of raw bytes between pipeline elements
use crate::{ #[cfg(feature = "os")]
process::{ChildPipe, ChildProcess}, use crate::process::{ChildPipe, ChildProcess};
ErrSpan, IntoSpanned, PipelineData, ShellError, Signals, Span, Type, Value, use crate::{ErrSpan, IntoSpanned, PipelineData, ShellError, Signals, Span, Type, Value};
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(unix)] #[cfg(unix)]
use std::os::fd::OwnedFd; use std::os::fd::OwnedFd;
@ -24,6 +23,7 @@ use std::{
pub enum ByteStreamSource { pub enum ByteStreamSource {
Read(Box<dyn Read + Send + 'static>), Read(Box<dyn Read + Send + 'static>),
File(File), File(File),
#[cfg(feature = "os")]
Child(Box<ChildProcess>), Child(Box<ChildProcess>),
} }
@ -32,6 +32,7 @@ impl ByteStreamSource {
match self { match self {
ByteStreamSource::Read(read) => Some(SourceReader::Read(read)), ByteStreamSource::Read(read) => Some(SourceReader::Read(read)),
ByteStreamSource::File(file) => Some(SourceReader::File(file)), ByteStreamSource::File(file) => Some(SourceReader::File(file)),
#[cfg(feature = "os")]
ByteStreamSource::Child(mut child) => child.stdout.take().map(|stdout| match stdout { ByteStreamSource::Child(mut child) => child.stdout.take().map(|stdout| match stdout {
ChildPipe::Pipe(pipe) => SourceReader::File(convert_file(pipe)), ChildPipe::Pipe(pipe) => SourceReader::File(convert_file(pipe)),
ChildPipe::Tee(tee) => SourceReader::Read(tee), ChildPipe::Tee(tee) => SourceReader::Read(tee),
@ -40,9 +41,16 @@ impl ByteStreamSource {
} }
/// Source is a `Child` or `File`, rather than `Read`. Currently affects trimming /// Source is a `Child` or `File`, rather than `Read`. Currently affects trimming
fn is_external(&self) -> bool { #[cfg(feature = "os")]
pub fn is_external(&self) -> bool {
matches!(self, ByteStreamSource::Child(..)) matches!(self, ByteStreamSource::Child(..))
} }
#[cfg(not(feature = "os"))]
pub fn is_external(&self) -> bool {
// without os support we never have externals
false
}
} }
impl Debug for ByteStreamSource { impl Debug for ByteStreamSource {
@ -50,6 +58,7 @@ impl Debug for ByteStreamSource {
match self { match self {
ByteStreamSource::Read(_) => f.debug_tuple("Read").field(&"..").finish(), ByteStreamSource::Read(_) => f.debug_tuple("Read").field(&"..").finish(),
ByteStreamSource::File(file) => f.debug_tuple("File").field(file).finish(), ByteStreamSource::File(file) => f.debug_tuple("File").field(file).finish(),
#[cfg(feature = "os")]
ByteStreamSource::Child(child) => f.debug_tuple("Child").field(child).finish(), ByteStreamSource::Child(child) => f.debug_tuple("Child").field(child).finish(),
} }
} }
@ -247,6 +256,7 @@ impl ByteStream {
/// ///
/// The type is implicitly `Unknown`, as it's not typically known whether child processes will /// The type is implicitly `Unknown`, as it's not typically known whether child processes will
/// return text or binary. /// return text or binary.
#[cfg(feature = "os")]
pub fn child(child: ChildProcess, span: Span) -> Self { pub fn child(child: ChildProcess, span: Span) -> Self {
Self::new( Self::new(
ByteStreamSource::Child(Box::new(child)), ByteStreamSource::Child(Box::new(child)),
@ -260,6 +270,7 @@ impl ByteStream {
/// ///
/// The type is implicitly `Unknown`, as it's not typically known whether stdin is text or /// The type is implicitly `Unknown`, as it's not typically known whether stdin is text or
/// binary. /// binary.
#[cfg(feature = "os")]
pub fn stdin(span: Span) -> Result<Self, ShellError> { pub fn stdin(span: Span) -> Result<Self, ShellError> {
let stdin = os_pipe::dup_stdin().err_span(span)?; let stdin = os_pipe::dup_stdin().err_span(span)?;
let source = ByteStreamSource::File(convert_file(stdin)); let source = ByteStreamSource::File(convert_file(stdin));
@ -271,6 +282,14 @@ impl ByteStream {
)) ))
} }
#[cfg(not(feature = "os"))]
pub fn stdin(span: Span) -> Result<Self, ShellError> {
Err(ShellError::DisabledOsSupport {
msg: "Stdin is not supported".to_string(),
span: Some(span),
})
}
/// Create a [`ByteStream`] from a generator function that writes data to the given buffer /// Create a [`ByteStream`] from a generator function that writes data to the given buffer
/// when called, and returns `Ok(false)` on end of stream. /// when called, and returns `Ok(false)` on end of stream.
pub fn from_fn( pub fn from_fn(
@ -432,6 +451,7 @@ impl ByteStream {
match self.stream { match self.stream {
ByteStreamSource::Read(..) => Err(self), ByteStreamSource::Read(..) => Err(self),
ByteStreamSource::File(file) => Ok(file.into()), ByteStreamSource::File(file) => Ok(file.into()),
#[cfg(feature = "os")]
ByteStreamSource::Child(child) => { ByteStreamSource::Child(child) => {
if let ChildProcess { if let ChildProcess {
stdout: Some(ChildPipe::Pipe(stdout)), stdout: Some(ChildPipe::Pipe(stdout)),
@ -453,6 +473,7 @@ impl ByteStream {
/// ///
/// This will only succeed if the [`ByteStreamSource`] of the [`ByteStream`] is [`Child`](ByteStreamSource::Child). /// This will only succeed if the [`ByteStreamSource`] of the [`ByteStream`] is [`Child`](ByteStreamSource::Child).
/// All other cases return an `Err` with the original [`ByteStream`] in it. /// All other cases return an `Err` with the original [`ByteStream`] in it.
#[cfg(feature = "os")]
pub fn into_child(self) -> Result<ChildProcess, Self> { pub fn into_child(self) -> Result<ChildProcess, Self> {
if let ByteStreamSource::Child(child) = self.stream { if let ByteStreamSource::Child(child) = self.stream {
Ok(*child) Ok(*child)
@ -477,6 +498,7 @@ impl ByteStream {
file.read_to_end(&mut buf).err_span(self.span)?; file.read_to_end(&mut buf).err_span(self.span)?;
Ok(buf) Ok(buf)
} }
#[cfg(feature = "os")]
ByteStreamSource::Child(child) => child.into_bytes(), ByteStreamSource::Child(child) => child.into_bytes(),
} }
} }
@ -551,6 +573,7 @@ impl ByteStream {
Ok(()) Ok(())
} }
ByteStreamSource::File(_) => Ok(()), ByteStreamSource::File(_) => Ok(()),
#[cfg(feature = "os")]
ByteStreamSource::Child(child) => child.wait(), ByteStreamSource::Child(child) => child.wait(),
} }
} }
@ -575,6 +598,7 @@ impl ByteStream {
ByteStreamSource::File(file) => { ByteStreamSource::File(file) => {
copy_with_signals(file, dest, span, signals)?; copy_with_signals(file, dest, span, signals)?;
} }
#[cfg(feature = "os")]
ByteStreamSource::Child(mut child) => { ByteStreamSource::Child(mut child) => {
// All `OutDest`s except `OutDest::PipeSeparate` will cause `stderr` to be `None`. // All `OutDest`s except `OutDest::PipeSeparate` will cause `stderr` to be `None`.
// Only `save`, `tee`, and `complete` set the stderr `OutDest` to `OutDest::PipeSeparate`, // Only `save`, `tee`, and `complete` set the stderr `OutDest` to `OutDest::PipeSeparate`,

View File

@ -9,8 +9,8 @@ version = "0.100.1"
[dependencies] [dependencies]
nu-parser = { version = "0.100.1", path = "../nu-parser" } nu-parser = { version = "0.100.1", path = "../nu-parser" }
nu-protocol = { version = "0.100.1", path = "../nu-protocol" } nu-protocol = { version = "0.100.1", path = "../nu-protocol", default-features = false }
nu-engine = { version = "0.100.1", path = "../nu-engine" } nu-engine = { version = "0.100.1", path = "../nu-engine", default-features = false }
miette = { workspace = true, features = ["fancy-no-backtrace"] } miette = { workspace = true, features = ["fancy-no-backtrace"] }
log = "0.4" log = "0.4"

View File

@ -14,9 +14,9 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-color-config = { path = "../nu-color-config", version = "0.100.1" } nu-color-config = { path = "../nu-color-config", version = "0.100.1" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
fancy-regex = { workspace = true } fancy-regex = { workspace = true }

View File

@ -14,6 +14,6 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
unicode-width = { workspace = true } unicode-width = { workspace = true }

View File

@ -17,6 +17,7 @@ bench = false
bench = false bench = false
[dependencies] [dependencies]
crossterm = { workspace = true, optional = true }
fancy-regex = { workspace = true } fancy-regex = { workspace = true }
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] } lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
log = { workspace = true } log = { workspace = true }
@ -26,6 +27,10 @@ serde = { workspace = true }
sys-locale = "0.3" sys-locale = "0.3"
unicase = "2.8.0" unicase = "2.8.0"
[features]
default = ["os"]
os = ["crossterm"]
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.9" crossterm_winapi = "0.9"

View File

@ -1,3 +1,4 @@
#[cfg(any(windows, unix))]
use std::path::Path; use std::path::Path;
#[cfg(unix)] #[cfg(unix)]
use { use {

View File

@ -12,7 +12,7 @@ pub use locale::get_system_locale;
pub use utils::{ pub use utils::{
enable_vt_processing, get_default_config, get_default_env, get_ls_colors, get_sample_config, enable_vt_processing, get_default_config, get_default_env, get_ls_colors, get_sample_config,
get_sample_env, get_scaffold_config, get_scaffold_env, stderr_write_all_and_flush, get_sample_env, get_scaffold_config, get_scaffold_env, stderr_write_all_and_flush,
stdout_write_all_and_flush, stdout_write_all_and_flush, terminal_size,
}; };
pub use casing::IgnoreCaseExt; pub use casing::IgnoreCaseExt;

View File

@ -1,7 +1,7 @@
#[cfg(windows)] #[cfg(windows)]
use crossterm_winapi::{ConsoleMode, Handle}; use crossterm_winapi::{ConsoleMode, Handle};
use lscolors::LsColors; use lscolors::LsColors;
use std::io::{Result, Write}; use std::io::{self, Result, Write};
pub fn enable_vt_processing() -> Result<()> { pub fn enable_vt_processing() -> Result<()> {
#[cfg(windows)] #[cfg(windows)]
@ -474,3 +474,17 @@ macro_rules! perf {
} }
}; };
} }
/// Returns the terminal size (columns, rows).
///
/// This utility variant allows getting a fallback value when compiling for wasm32 without having
/// to rearrange other bits of the codebase.
///
/// See [`crossterm::terminal::size`].
pub fn terminal_size() -> io::Result<(u16, u16)> {
#[cfg(feature = "os")]
return crossterm::terminal::size();
#[cfg(not(feature = "os"))]
return Err(io::Error::from(io::ErrorKind::Unsupported));
}

View File

@ -10,7 +10,7 @@ name = "nu_plugin_custom_values"
bench = false bench = false
[dependencies] [dependencies]
nu-plugin = { path = "../nu-plugin", version = "0.100.1" } nu-plugin = { path = "../nu-plugin", version = "0.100.1", features = [] }
nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] } nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] }
serde = { workspace = true } serde = { workspace = true }
typetag = "0.2" typetag = "0.2"

View File

@ -11,9 +11,9 @@ version = "0.100.1"
[dependencies] [dependencies]
nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.100.1" } nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
[dev-dependencies] [dev-dependencies]
chrono = { workspace = true } chrono = { workspace = true }

View File

@ -363,6 +363,40 @@ export def build [
} }
} }
# build crates for wasm
export def "build wasm" [] {
^rustup target add wasm32-unknown-unknown
# these crates should compile for wasm
let compatible_crates = [
"nu-cmd-base",
"nu-cmd-extra",
"nu-cmd-lang",
"nu-color-config",
"nu-command",
"nu-derive-value",
"nu-engine",
"nu-glob",
"nu-json",
"nu-parser",
"nu-path",
"nu-pretty-hex",
"nu-protocol",
"nu-std",
"nu-system",
"nu-table",
"nu-term-grid",
"nu-utils",
"nuon"
]
for crate in $compatible_crates {
print $'(char nl)Building ($crate) for wasm'
print '----------------------------'
^cargo build -p $crate --target wasm32-unknown-unknown --no-default-features
}
}
def "nu-complete list features" [] { def "nu-complete list features" [] {
open Cargo.toml | get features | transpose feature dependencies | get feature open Cargo.toml | get features | transpose feature dependencies | get feature
} }