forked from extern/nushell
Add support for 'open' (#573)
This commit is contained in:
parent
1efae6876d
commit
a811eee6b8
@ -149,6 +149,7 @@ pub fn create_default_context() -> EngineState {
|
|||||||
Ls,
|
Ls,
|
||||||
Mkdir,
|
Mkdir,
|
||||||
Mv,
|
Mv,
|
||||||
|
Open,
|
||||||
Rm,
|
Rm,
|
||||||
Touch,
|
Touch,
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use nu_engine::eval_expression;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, DataSource, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata,
|
Category, DataSource, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata,
|
||||||
ShellError, Signature, Span, SyntaxShape, Value,
|
ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
@ -42,11 +42,11 @@ impl Command for Ls {
|
|||||||
"Only print the file names and not the path",
|
"Only print the file names and not the path",
|
||||||
Some('s'),
|
Some('s'),
|
||||||
)
|
)
|
||||||
.switch(
|
// .switch(
|
||||||
"du",
|
// "du",
|
||||||
"Display the apparent directory size in place of the directory metadata size",
|
// "Display the apparent directory size in place of the directory metadata size",
|
||||||
Some('d'),
|
// Some('d'),
|
||||||
)
|
// )
|
||||||
.category(Category::FileSystem)
|
.category(Category::FileSystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,22 +63,20 @@ impl Command for Ls {
|
|||||||
|
|
||||||
let call_span = call.head;
|
let call_span = call.head;
|
||||||
|
|
||||||
let (pattern, arg_span) = if let Some(expr) = call.positional.get(0) {
|
let (pattern, arg_span) =
|
||||||
let result = eval_expression(engine_state, stack, expr)?;
|
if let Some(mut result) = call.opt::<Spanned<String>>(engine_state, stack, 0)? {
|
||||||
let mut result = result.as_string()?;
|
let path = std::path::Path::new(&result.item);
|
||||||
|
if path.is_dir() {
|
||||||
let path = std::path::Path::new(&result);
|
if !result.item.ends_with(std::path::MAIN_SEPARATOR) {
|
||||||
if path.is_dir() {
|
result.item.push(std::path::MAIN_SEPARATOR);
|
||||||
if !result.ends_with(std::path::MAIN_SEPARATOR) {
|
}
|
||||||
result.push(std::path::MAIN_SEPARATOR);
|
result.item.push('*');
|
||||||
}
|
}
|
||||||
result.push('*');
|
|
||||||
}
|
|
||||||
|
|
||||||
(result, expr.span)
|
(result.item, result.span)
|
||||||
} else {
|
} else {
|
||||||
("*".into(), call_span)
|
("*".into(), call_span)
|
||||||
};
|
};
|
||||||
|
|
||||||
let glob = glob::glob(&pattern).map_err(|err| {
|
let glob = glob::glob(&pattern).map_err(|err| {
|
||||||
nu_protocol::ShellError::SpannedLabeledError(
|
nu_protocol::ShellError::SpannedLabeledError(
|
||||||
|
@ -3,6 +3,7 @@ mod cp;
|
|||||||
mod ls;
|
mod ls;
|
||||||
mod mkdir;
|
mod mkdir;
|
||||||
mod mv;
|
mod mv;
|
||||||
|
mod open;
|
||||||
mod rm;
|
mod rm;
|
||||||
mod touch;
|
mod touch;
|
||||||
mod util;
|
mod util;
|
||||||
@ -12,5 +13,6 @@ pub use cp::Cp;
|
|||||||
pub use ls::Ls;
|
pub use ls::Ls;
|
||||||
pub use mkdir::Mkdir;
|
pub use mkdir::Mkdir;
|
||||||
pub use mv::Mv;
|
pub use mv::Mv;
|
||||||
|
pub use open::Open;
|
||||||
pub use rm::Rm;
|
pub use rm::Rm;
|
||||||
pub use touch::Touch;
|
pub use touch::Touch;
|
||||||
|
160
crates/nu-command/src/filesystem/open.rs
Normal file
160
crates/nu-command/src/filesystem/open.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
ByteStream, Category, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
use std::io::{BufRead, BufReader, Read};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Open;
|
||||||
|
|
||||||
|
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
||||||
|
impl Command for Open {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"open"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"List the files in a directory."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("open")
|
||||||
|
.required(
|
||||||
|
"filename",
|
||||||
|
SyntaxShape::GlobPattern,
|
||||||
|
"the glob pattern to use",
|
||||||
|
)
|
||||||
|
.switch("raw", "open file as binary", Some('r'))
|
||||||
|
.category(Category::FileSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let raw = call.has_flag("raw");
|
||||||
|
|
||||||
|
let call_span = call.head;
|
||||||
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
|
let path = call.req::<Spanned<String>>(engine_state, stack, 0)?;
|
||||||
|
let arg_span = path.span;
|
||||||
|
let path = Path::new(&path.item);
|
||||||
|
|
||||||
|
if permission_denied(&path) {
|
||||||
|
#[cfg(unix)]
|
||||||
|
let error_msg = format!(
|
||||||
|
"The permissions of {:o} do not allow access for this user",
|
||||||
|
path.metadata()
|
||||||
|
.expect("this shouldn't be called since we already know there is a dir")
|
||||||
|
.permissions()
|
||||||
|
.mode()
|
||||||
|
& 0o0777
|
||||||
|
);
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let error_msg = String::from("Permission denied");
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
Value::Error {
|
||||||
|
error: ShellError::SpannedLabeledError(
|
||||||
|
"Permission denied".into(),
|
||||||
|
error_msg,
|
||||||
|
arg_span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let file = match std::fs::File::open(path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(err) => {
|
||||||
|
return Ok(PipelineData::Value(
|
||||||
|
Value::Error {
|
||||||
|
error: ShellError::SpannedLabeledError(
|
||||||
|
"Permission denied".into(),
|
||||||
|
err.to_string(),
|
||||||
|
arg_span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let buf_reader = BufReader::new(file);
|
||||||
|
|
||||||
|
let output = PipelineData::ByteStream(
|
||||||
|
ByteStream {
|
||||||
|
stream: Box::new(BufferedReader { input: buf_reader }),
|
||||||
|
ctrlc,
|
||||||
|
},
|
||||||
|
call_span,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ext = if raw {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
path.extension()
|
||||||
|
.map(|name| name.to_string_lossy().to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ext) = ext {
|
||||||
|
match engine_state.find_decl(format!("from {}", ext).as_bytes()) {
|
||||||
|
Some(converter_id) => engine_state.get_decl(converter_id).run(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&Call::new(),
|
||||||
|
output,
|
||||||
|
),
|
||||||
|
None => Ok(output),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permission_denied(dir: impl AsRef<Path>) -> bool {
|
||||||
|
match dir.as_ref().read_dir() {
|
||||||
|
Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied),
|
||||||
|
Ok(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BufferedReader<R: Read> {
|
||||||
|
input: BufReader<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> Iterator for BufferedReader<R> {
|
||||||
|
type Item = Result<Vec<u8>, ShellError>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let buffer = self.input.fill_buf();
|
||||||
|
match buffer {
|
||||||
|
Ok(s) => {
|
||||||
|
let result = s.to_vec();
|
||||||
|
|
||||||
|
let buffer_len = s.len();
|
||||||
|
|
||||||
|
if buffer_len == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.input.consume(buffer_len);
|
||||||
|
|
||||||
|
Some(Ok(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Some(Err(ShellError::IOError(e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -113,7 +113,10 @@ impl Command for Each {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(move |(idx, x)| {
|
.map(move |(idx, x)| {
|
||||||
let x = Value::Binary { val: x, span };
|
let x = match x {
|
||||||
|
Ok(x) => Value::Binary { val: x, span },
|
||||||
|
Err(err) => return Value::Error { error: err },
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(var) = block.signature.get_positional(0) {
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
if let Some(var_id) = &var.var_id {
|
if let Some(var_id) = &var.var_id {
|
||||||
|
@ -227,7 +227,11 @@ impl Command for ParEach {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.par_bridge()
|
.par_bridge()
|
||||||
.map(move |(idx, x)| {
|
.map(move |(idx, x)| {
|
||||||
let x = Value::Binary { val: x, span };
|
let x = match x {
|
||||||
|
Ok(x) => Value::Binary { val: x, span },
|
||||||
|
Err(err) => return Value::Error { error: err }.into_pipeline_data(),
|
||||||
|
};
|
||||||
|
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
let mut stack = stack.clone();
|
let mut stack = stack.clone();
|
||||||
|
@ -56,7 +56,7 @@ impl Command for Wrap {
|
|||||||
}
|
}
|
||||||
.into_pipeline_data()),
|
.into_pipeline_data()),
|
||||||
PipelineData::ByteStream(stream, ..) => Ok(Value::Binary {
|
PipelineData::ByteStream(stream, ..) => Ok(Value::Binary {
|
||||||
val: stream.into_vec(),
|
val: stream.into_vec()?,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.into_pipeline_data()),
|
.into_pipeline_data()),
|
||||||
|
@ -45,7 +45,7 @@ impl Command for Decode {
|
|||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::ByteStream(stream, ..) => {
|
PipelineData::ByteStream(stream, ..) => {
|
||||||
let bytes: Vec<u8> = stream.flatten().collect();
|
let bytes: Vec<u8> = stream.into_vec()?;
|
||||||
|
|
||||||
let encoding = match Encoding::for_label(encoding.item.as_bytes()) {
|
let encoding = match Encoding::for_label(encoding.item.as_bytes()) {
|
||||||
None => Err(ShellError::SpannedLabeledError(
|
None => Err(ShellError::SpannedLabeledError(
|
||||||
|
@ -359,11 +359,11 @@ impl ChannelReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for ChannelReceiver {
|
impl Iterator for ChannelReceiver {
|
||||||
type Item = Vec<u8>;
|
type Item = Result<Vec<u8>, ShellError>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
match self.rx.recv() {
|
match self.rx.recv() {
|
||||||
Ok(v) => Some(v),
|
Ok(v) => Some(Ok(v)),
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,9 @@ impl Command for Table {
|
|||||||
StringStream::from_stream(
|
StringStream::from_stream(
|
||||||
stream.map(move |x| {
|
stream.map(move |x| {
|
||||||
Ok(if x.iter().all(|x| x.is_ascii()) {
|
Ok(if x.iter().all(|x| x.is_ascii()) {
|
||||||
format!("{}", String::from_utf8_lossy(&x))
|
format!("{}", String::from_utf8_lossy(&x?))
|
||||||
} else {
|
} else {
|
||||||
format!("{}\n", nu_pretty_hex::pretty_hex(&x))
|
format!("{}\n", nu_pretty_hex::pretty_hex(&x?))
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
ctrlc,
|
ctrlc,
|
||||||
|
@ -89,7 +89,7 @@ impl Command for PluginDeclaration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipelineData::ByteStream(stream, ..) => {
|
PipelineData::ByteStream(stream, ..) => {
|
||||||
let val = stream.into_vec();
|
let val = stream.into_vec()?;
|
||||||
|
|
||||||
Value::Binary {
|
Value::Binary {
|
||||||
val,
|
val,
|
||||||
|
@ -87,10 +87,21 @@ impl PipelineData {
|
|||||||
span, // FIXME?
|
span, // FIXME?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipelineData::ByteStream(s, ..) => Value::Binary {
|
PipelineData::ByteStream(s, ..) => {
|
||||||
val: s.flatten().collect(),
|
let mut output = vec![];
|
||||||
span, // FIXME?
|
|
||||||
},
|
for item in s {
|
||||||
|
match item {
|
||||||
|
Ok(s) => output.extend(&s),
|
||||||
|
Err(err) => return Value::Error { error: err },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Binary {
|
||||||
|
val: output,
|
||||||
|
span, // FIXME?
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +121,7 @@ impl PipelineData {
|
|||||||
PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)),
|
PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)),
|
||||||
PipelineData::StringStream(s, ..) => s.into_string(separator),
|
PipelineData::StringStream(s, ..) => s.into_string(separator),
|
||||||
PipelineData::ByteStream(s, ..) => {
|
PipelineData::ByteStream(s, ..) => {
|
||||||
Ok(String::from_utf8_lossy(&s.flatten().collect::<Vec<_>>()).to_string())
|
Ok(String::from_utf8_lossy(&s.into_vec()?).to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,9 +335,12 @@ impl Iterator for PipelineIterator {
|
|||||||
},
|
},
|
||||||
Err(err) => Value::Error { error: err },
|
Err(err) => Value::Error { error: err },
|
||||||
}),
|
}),
|
||||||
PipelineData::ByteStream(stream, span, ..) => stream.next().map(|x| Value::Binary {
|
PipelineData::ByteStream(stream, span, ..) => stream.next().map(|x| match x {
|
||||||
val: x,
|
Ok(x) => Value::Binary {
|
||||||
span: *span,
|
val: x,
|
||||||
|
span: *span,
|
||||||
|
},
|
||||||
|
Err(err) => Value::Error { error: err },
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,17 @@ use std::{
|
|||||||
/// A single buffer of binary data streamed over multiple parts. Optionally contains ctrl-c that can be used
|
/// A single buffer of binary data streamed over multiple parts. Optionally contains ctrl-c that can be used
|
||||||
/// to break the stream.
|
/// to break the stream.
|
||||||
pub struct ByteStream {
|
pub struct ByteStream {
|
||||||
pub stream: Box<dyn Iterator<Item = Vec<u8>> + Send + 'static>,
|
pub stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>,
|
||||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||||
}
|
}
|
||||||
impl ByteStream {
|
impl ByteStream {
|
||||||
pub fn into_vec(self) -> Vec<u8> {
|
pub fn into_vec(self) -> Result<Vec<u8>, ShellError> {
|
||||||
self.flatten().collect::<Vec<u8>>()
|
let mut output = vec![];
|
||||||
|
for item in self.stream {
|
||||||
|
output.append(&mut item?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Debug for ByteStream {
|
impl Debug for ByteStream {
|
||||||
@ -25,7 +30,7 @@ impl Debug for ByteStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for ByteStream {
|
impl Iterator for ByteStream {
|
||||||
type Item = Vec<u8>;
|
type Item = Result<Vec<u8>, ShellError>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if let Some(ctrlc) = &self.ctrlc {
|
if let Some(ctrlc) = &self.ctrlc {
|
||||||
|
@ -594,9 +594,9 @@ fn print_pipeline_data(
|
|||||||
PipelineData::ByteStream(stream, _, _) => {
|
PipelineData::ByteStream(stream, _, _) => {
|
||||||
for v in stream {
|
for v in stream {
|
||||||
let s = if v.iter().all(|x| x.is_ascii()) {
|
let s = if v.iter().all(|x| x.is_ascii()) {
|
||||||
format!("{}", String::from_utf8_lossy(&v))
|
format!("{}", String::from_utf8_lossy(&v?))
|
||||||
} else {
|
} else {
|
||||||
format!("{}\n", nu_pretty_hex::pretty_hex(&v))
|
format!("{}\n", nu_pretty_hex::pretty_hex(&v?))
|
||||||
};
|
};
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user