nushell/crates/nu-cli/src/commands/open.rs

260 lines
9.2 KiB
Rust
Raw Normal View History

use crate::commands::WholeStreamCommand;
2019-05-28 06:00:00 +02:00
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::{AnchorLocation, Span, Tagged};
2019-06-03 09:41:28 +02:00
use std::path::{Path, PathBuf};
2019-07-24 00:22:11 +02:00
pub struct Open;
2019-06-22 05:43:37 +02:00
#[derive(Deserialize)]
pub struct OpenArgs {
path: Tagged<PathBuf>,
raw: Tagged<bool>,
}
2020-05-29 10:22:52 +02:00
#[async_trait]
impl WholeStreamCommand for Open {
2019-08-02 21:15:07 +02:00
fn name(&self) -> &str {
"open"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
2019-10-28 06:15:35 +01:00
.required(
"path",
SyntaxShape::Path,
"the file path to load values from",
)
.switch(
"raw",
"load content as a string instead of a table",
Some('r'),
)
2019-08-02 21:15:07 +02:00
}
fn usage(&self) -> &str {
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')"
}
2020-05-29 10:22:52 +02:00
async fn run(
2019-07-24 00:22:11 +02:00
&self,
args: CommandArgs,
registry: &CommandRegistry,
2019-08-24 21:36:19 +02:00
) -> Result<OutputStream, ShellError> {
open(args, registry).await
2019-08-02 21:15:07 +02:00
}
2020-05-18 17:40:44 +02:00
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Opens \"users.csv\" and creates a table from the data",
example: "open users.csv",
2020-05-18 17:40:44 +02:00
result: None,
}]
}
2019-08-02 21:15:07 +02:00
}
2019-07-24 00:22:11 +02:00
async fn open(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let cwd = PathBuf::from(args.shell_manager.path());
let full_path = cwd;
let registry = registry.clone();
2019-06-22 05:43:37 +02:00
let (OpenArgs { path, raw }, _) = args.process(&registry).await?;
let result = fetch(&full_path, &path.item, path.tag.span).await;
2019-08-25 15:57:47 +02:00
let (file_extension, contents, contents_tag) = result?;
2019-08-25 15:57:47 +02:00
let file_extension = if raw.item {
None
} else {
// If the extension could not be determined via mimetype, try to use the path
// extension. Some file types do not declare their mimetypes (such as bson files).
file_extension.or_else(|| path.extension().map(|x| x.to_string_lossy().to_string()))
2019-08-02 21:15:07 +02:00
};
2019-07-24 00:22:11 +02:00
let tagged_contents = contents.into_value(&contents_tag);
if let Some(extension) = file_extension {
Ok(OutputStream::one(ReturnSuccess::action(
CommandAction::AutoConvert(tagged_contents, extension),
)))
} else {
Ok(OutputStream::one(ReturnSuccess::value(tagged_contents)))
}
2019-06-22 05:43:37 +02:00
}
2019-08-24 21:36:19 +02:00
pub async fn fetch(
2019-07-02 09:56:20 +02:00
cwd: &PathBuf,
location: &PathBuf,
2019-09-18 08:37:04 +02:00
span: Span,
) -> Result<(Option<String>, UntaggedValue, Tag), ShellError> {
let mut cwd = cwd.clone();
2019-09-03 08:04:46 +02:00
cwd.push(Path::new(location));
if let Ok(cwd) = dunce::canonicalize(cwd) {
match std::fs::read(&cwd) {
Ok(bytes) => match std::str::from_utf8(&bytes) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
2019-09-18 08:37:04 +02:00
},
)),
2019-09-03 08:04:46 +02:00
Err(_) => {
//Non utf8 data.
match (bytes.get(0), bytes.get(1)) {
(Some(x), Some(y)) if *x == 0xff && *y == 0xfe => {
// Possibly UTF-16 little endian
let utf16 = read_le_u16(&bytes[2..]);
2019-08-12 06:11:42 +02:00
2019-09-03 08:04:46 +02:00
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
2019-09-18 08:37:04 +02:00
},
2019-09-03 08:04:46 +02:00
)),
Err(_) => Ok((
2019-08-12 06:11:42 +02:00
None,
UntaggedValue::binary(bytes),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
2019-09-18 08:37:04 +02:00
},
2019-09-03 08:04:46 +02:00
)),
2019-08-12 06:11:42 +02:00
}
2019-09-03 08:04:46 +02:00
} else {
Ok((
None,
UntaggedValue::binary(bytes),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
2019-09-18 08:37:04 +02:00
},
2019-09-03 08:04:46 +02:00
))
2019-08-12 06:11:42 +02:00
}
2019-09-03 08:04:46 +02:00
}
(Some(x), Some(y)) if *x == 0xfe && *y == 0xff => {
// Possibly UTF-16 big endian
let utf16 = read_be_u16(&bytes[2..]);
2019-08-12 06:11:42 +02:00
2019-09-03 08:04:46 +02:00
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
2019-09-18 08:37:04 +02:00
},
2019-09-03 08:04:46 +02:00
)),
Err(_) => Ok((
2019-08-12 06:11:42 +02:00
None,
UntaggedValue::binary(bytes),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
2019-09-18 08:37:04 +02:00
},
2019-09-03 08:04:46 +02:00
)),
2019-08-12 06:11:42 +02:00
}
2019-09-03 08:04:46 +02:00
} else {
Ok((
None,
UntaggedValue::binary(bytes),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
2019-09-18 08:37:04 +02:00
},
2019-09-03 08:04:46 +02:00
))
2019-08-12 06:11:42 +02:00
}
}
2019-09-03 08:04:46 +02:00
_ => Ok((
None,
UntaggedValue::binary(bytes),
2019-09-18 08:37:04 +02:00
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
2019-09-18 08:37:04 +02:00
},
2019-09-03 08:04:46 +02:00
)),
2019-08-12 06:11:42 +02:00
}
2019-08-10 22:33:22 +02:00
}
2019-09-03 08:04:46 +02:00
},
Err(_) => Err(ShellError::labeled_error(
"File could not be opened",
"file not found",
span,
)),
}
2019-09-03 08:04:46 +02:00
} else {
Err(ShellError::labeled_error(
2019-09-03 08:04:46 +02:00
"File could not be opened",
"file not found",
2019-09-18 08:37:04 +02:00
span,
))
}
}
2019-08-12 06:11:42 +02:00
fn read_le_u16(input: &[u8]) -> Option<Vec<u16>> {
if input.len() % 2 != 0 || input.len() < 2 {
None
} else {
let mut result = vec![];
let mut pos = 0;
while pos < input.len() {
result.push(u16::from_le_bytes([input[pos], input[pos + 1]]));
pos += 2;
}
Some(result)
}
}
fn read_be_u16(input: &[u8]) -> Option<Vec<u16>> {
if input.len() % 2 != 0 || input.len() < 2 {
None
} else {
let mut result = vec![];
let mut pos = 0;
while pos < input.len() {
result.push(u16::from_be_bytes([input[pos], input[pos + 1]]));
pos += 2;
}
Some(result)
}
}
#[cfg(test)]
mod tests {
use super::Open;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Open {})
}
}