nushell/crates/nu-parser/src/parser_path.rs
Devyn Cairns 1f4131532d
Deprecate register and add plugin use (#12607)
# Description

Adds a new keyword, `plugin use`. Unlike `register`, this merely loads
the signatures from the plugin cache file. The file is configurable with
the `--plugin-config` option either to `nu` or to `plugin use` itself,
just like the other `plugin` family of commands. At the REPL, one might
do this to replace `register`:

```nushell
> plugin add ~/.cargo/bin/nu_plugin_foo
> plugin use foo
```

This will not work in a script, because `plugin use` is a keyword and
`plugin add` does not evaluate at parse time (intentionally). This means
we no longer run random binaries during parse.

The `--plugins` option has been added to allow running `nu` with certain
plugins in one step. This is used especially for the `nu_with_plugins!`
test macro, but I'd imagine is generally useful. The only weird quirk is
that it has to be a list, and we don't really do this for any of our
other CLI args at the moment.

`register` now prints a deprecation parse warning.

This should fix #11923, as we now have a complete alternative to
`register`.

# User-Facing Changes

- Add `plugin use` command
- Deprecate `register`
- Add `--plugins` option to `nu` to replace a common use of `register`

# Tests + Formatting

I think I've tested it thoroughly enough and every existing test passes.
Testing nu CLI options and alternate config files is a little hairy and
I wish there were some more generic helpers for this, so this will go on
my TODO list for refactoring.

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

# After Submitting

- [ ] Update plugins sections of book
- [ ] Release notes
2024-04-23 06:37:50 -05:00

152 lines
4.8 KiB
Rust

use nu_protocol::engine::{StateWorkingSet, VirtualPath};
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
/// An abstraction over a PathBuf that can have virtual paths (files and directories). Virtual
/// paths always exist and represent a way to ship Nushell code inside the binary without requiring
/// paths to be present in the file system.
///
/// Created from VirtualPath found in the engine state.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ParserPath {
RealPath(PathBuf),
VirtualFile(PathBuf, usize),
VirtualDir(PathBuf, Vec<ParserPath>),
}
impl ParserPath {
pub fn is_dir(&self) -> bool {
match self {
ParserPath::RealPath(p) => p.is_dir(),
ParserPath::VirtualFile(..) => false,
ParserPath::VirtualDir(..) => true,
}
}
pub fn is_file(&self) -> bool {
match self {
ParserPath::RealPath(p) => p.is_file(),
ParserPath::VirtualFile(..) => true,
ParserPath::VirtualDir(..) => false,
}
}
pub fn exists(&self) -> bool {
match self {
ParserPath::RealPath(p) => p.exists(),
ParserPath::VirtualFile(..) => true,
ParserPath::VirtualDir(..) => true,
}
}
pub fn path(&self) -> &Path {
match self {
ParserPath::RealPath(p) => p,
ParserPath::VirtualFile(p, _) => p,
ParserPath::VirtualDir(p, _) => p,
}
}
pub fn path_buf(self) -> PathBuf {
match self {
ParserPath::RealPath(p) => p,
ParserPath::VirtualFile(p, _) => p,
ParserPath::VirtualDir(p, _) => p,
}
}
pub fn parent(&self) -> Option<&Path> {
match self {
ParserPath::RealPath(p) => p.parent(),
ParserPath::VirtualFile(p, _) => p.parent(),
ParserPath::VirtualDir(p, _) => p.parent(),
}
}
pub fn read_dir(&self) -> Option<Vec<ParserPath>> {
match self {
ParserPath::RealPath(p) => p.read_dir().ok().map(|read_dir| {
read_dir
.flatten()
.map(|dir_entry| ParserPath::RealPath(dir_entry.path()))
.collect()
}),
ParserPath::VirtualFile(..) => None,
ParserPath::VirtualDir(_, files) => Some(files.clone()),
}
}
pub fn file_stem(&self) -> Option<&OsStr> {
self.path().file_stem()
}
pub fn extension(&self) -> Option<&OsStr> {
self.path().extension()
}
pub fn join(self, path: impl AsRef<Path>) -> ParserPath {
match self {
ParserPath::RealPath(p) => ParserPath::RealPath(p.join(path)),
ParserPath::VirtualFile(p, file_id) => ParserPath::VirtualFile(p.join(path), file_id),
ParserPath::VirtualDir(p, entries) => {
let new_p = p.join(path);
let mut pp = ParserPath::RealPath(new_p.clone());
for entry in entries {
if new_p == entry.path() {
pp = entry.clone();
}
}
pp
}
}
}
pub fn open<'a>(
&'a self,
working_set: &'a StateWorkingSet,
) -> std::io::Result<Box<dyn std::io::Read + 'a>> {
match self {
ParserPath::RealPath(p) => {
std::fs::File::open(p).map(|f| Box::new(f) as Box<dyn std::io::Read>)
}
ParserPath::VirtualFile(_, file_id) => working_set
.get_contents_of_file(*file_id)
.map(|bytes| Box::new(bytes) as Box<dyn std::io::Read>)
.ok_or(std::io::ErrorKind::NotFound.into()),
ParserPath::VirtualDir(..) => Err(std::io::ErrorKind::NotFound.into()),
}
}
pub fn read<'a>(&'a self, working_set: &'a StateWorkingSet) -> Option<Vec<u8>> {
self.open(working_set)
.and_then(|mut reader| {
let mut vec = vec![];
reader.read_to_end(&mut vec)?;
Ok(vec)
})
.ok()
}
pub fn from_virtual_path(
working_set: &StateWorkingSet,
name: &str,
virtual_path: &VirtualPath,
) -> Self {
match virtual_path {
VirtualPath::File(file_id) => ParserPath::VirtualFile(PathBuf::from(name), *file_id),
VirtualPath::Dir(entries) => ParserPath::VirtualDir(
PathBuf::from(name),
entries
.iter()
.map(|virtual_path_id| {
let (virt_name, virt_path) = working_set.get_virtual_path(*virtual_path_id);
ParserPath::from_virtual_path(working_set, virt_name, virt_path)
})
.collect(),
),
}
}
}