mirror of
https://github.com/nushell/nushell.git
synced 2024-11-30 04:14:17 +01:00
0c67d742f0
This pr fix clippy warnings in latest clippy version(1.72.0): Unfortunally it's not easy to handle for [try fold](https://rust-lang.github.io/rust-clippy/master/index.html#/manual_try_fold) warning in `start command` Refer to known issue: > This lint doesn’t take into account whether a function does something on the failure case, i.e., whether short-circuiting will affect behavior. Refactoring to try_fold is not desirable in those cases. That's the case for our code, which does something on the failure case. So this pr is making a little refactor on `try_commands`.
197 lines
6.9 KiB
Rust
197 lines
6.9 KiB
Rust
use itertools::Itertools;
|
|
use nu_engine::env_to_strings;
|
|
use nu_engine::CallExt;
|
|
use nu_path::canonicalize_with;
|
|
use nu_protocol::ast::Call;
|
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
use nu_protocol::{
|
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
|
};
|
|
use std::ffi::{OsStr, OsString};
|
|
use std::path::Path;
|
|
use std::process::Stdio;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Start;
|
|
|
|
impl Command for Start {
|
|
fn name(&self) -> &str {
|
|
"start"
|
|
}
|
|
|
|
fn usage(&self) -> &str {
|
|
"Open a folder, file or website in the default application or viewer."
|
|
}
|
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
vec!["load", "folder", "directory", "run", "open"]
|
|
}
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
Signature::build("start")
|
|
.input_output_types(vec![(Type::Nothing, Type::Any), (Type::String, Type::Any)])
|
|
.required("path", SyntaxShape::String, "path to open")
|
|
.category(Category::FileSystem)
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
_input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let path = call.req::<Spanned<String>>(engine_state, stack, 0)?;
|
|
let path = Spanned {
|
|
item: nu_utils::strip_ansi_string_unlikely(path.item),
|
|
span: path.span,
|
|
};
|
|
let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
|
// only check if file exists in current current directory
|
|
let file_path = Path::new(path_no_whitespace);
|
|
if file_path.exists() {
|
|
open_path(path_no_whitespace, engine_state, stack, path.span)?;
|
|
} else if file_path.starts_with("https://") || file_path.starts_with("http://") {
|
|
let url = url::Url::parse(&path.item).map_err(|_| {
|
|
ShellError::GenericError(
|
|
format!("Cannot parse url: {}", &path.item),
|
|
"".to_string(),
|
|
Some(path.span),
|
|
Some("cannot parse".to_string()),
|
|
Vec::new(),
|
|
)
|
|
})?;
|
|
open_path(url.as_str(), engine_state, stack, path.span)?;
|
|
} else {
|
|
// try to distinguish between file not found and opening url without prefix
|
|
if let Ok(canon_path) =
|
|
canonicalize_with(path_no_whitespace, std::env::current_dir()?.as_path())
|
|
{
|
|
open_path(canon_path, engine_state, stack, path.span)?;
|
|
} else {
|
|
// open crate does not allow opening URL without prefix
|
|
let path_with_prefix = Path::new("https://").join(&path.item);
|
|
let common_domains = ["com", "net", "org", "edu", "sh"];
|
|
if let Some(url) = path_with_prefix.to_str() {
|
|
let url = url::Url::parse(url).map_err(|_| {
|
|
ShellError::GenericError(
|
|
format!("Cannot parse url: {}", &path.item),
|
|
"".to_string(),
|
|
Some(path.span),
|
|
Some("cannot parse".to_string()),
|
|
Vec::new(),
|
|
)
|
|
})?;
|
|
if let Some(domain) = url.host() {
|
|
let domain = domain.to_string();
|
|
let ext = Path::new(&domain).extension().and_then(|s| s.to_str());
|
|
if let Some(url_ext) = ext {
|
|
if common_domains.contains(&url_ext) {
|
|
open_path(url.as_str(), engine_state, stack, path.span)?;
|
|
}
|
|
}
|
|
}
|
|
return Err(ShellError::GenericError(
|
|
format!("Cannot find file or url: {}", &path.item),
|
|
"".to_string(),
|
|
Some(path.span),
|
|
Some("Use prefix https:// to disambiguate URLs from files".to_string()),
|
|
Vec::new(),
|
|
));
|
|
}
|
|
};
|
|
}
|
|
Ok(PipelineData::Empty)
|
|
}
|
|
|
|
fn examples(&self) -> Vec<nu_protocol::Example> {
|
|
vec![
|
|
Example {
|
|
description: "Open a text file with the default text editor",
|
|
example: "start file.txt",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open an image with the default image viewer",
|
|
example: "start file.jpg",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open the current directory with the default file manager",
|
|
example: "start .",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open a pdf with the default pdf viewer",
|
|
example: "start file.pdf",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open a website with default browser",
|
|
example: "start https://www.nushell.sh",
|
|
result: None,
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
fn open_path(
|
|
path: impl AsRef<OsStr>,
|
|
engine_state: &EngineState,
|
|
stack: &Stack,
|
|
span: Span,
|
|
) -> Result<(), ShellError> {
|
|
try_commands(open::commands(path), engine_state, stack, span)
|
|
}
|
|
|
|
fn try_commands(
|
|
commands: Vec<std::process::Command>,
|
|
engine_state: &EngineState,
|
|
stack: &Stack,
|
|
span: Span,
|
|
) -> Result<(), ShellError> {
|
|
let env_vars_str = env_to_strings(engine_state, stack)?;
|
|
let cmd_run_result = commands.into_iter().map(|mut cmd| {
|
|
let status = cmd
|
|
.envs(&env_vars_str)
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status();
|
|
match status {
|
|
Ok(status) if status.success() => Ok(()),
|
|
Ok(status) => Err(format!(
|
|
"\nCommand `{}` failed with {}",
|
|
format_command(&cmd),
|
|
status
|
|
)),
|
|
Err(err) => Err(format!(
|
|
"\nCommand `{}` failed with {}",
|
|
format_command(&cmd),
|
|
err
|
|
)),
|
|
}
|
|
});
|
|
|
|
for one_result in cmd_run_result {
|
|
if let Err(err_msg) = one_result {
|
|
return Err(ShellError::ExternalCommand {
|
|
label: "No command found to start with this path".to_string(),
|
|
help: "Try different path or install appropriate command\n".to_string() + &err_msg,
|
|
span,
|
|
});
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn format_command(command: &std::process::Command) -> String {
|
|
let parts_iter = std::iter::repeat(command.get_program())
|
|
.take(1)
|
|
.chain(command.get_args());
|
|
Itertools::intersperse(parts_iter, " ".as_ref())
|
|
.collect::<OsString>()
|
|
.to_string_lossy()
|
|
.into_owned()
|
|
}
|