forked from extern/nushell
start
command in nushell (#1727)
* mvp for start command * modified the signature of the start command * parse filenames * working model for macos is done * refactored to read from pipes * start command works well on macos; manual testing reveals need of --args flag support * implemented start error; color printing of warning and errors * ran clippy and fixed warnings * fix a clippy lint that was caught in pipeline * fix dead code clippy lint for windows * add cfg annotation to import
This commit is contained in:
parent
e04b89f747
commit
9a94b3c656
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -2427,6 +2427,20 @@ dependencies = [
|
||||
"nu-source",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_start"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"nu-build",
|
||||
"nu-errors",
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
"nu-source",
|
||||
"open",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_str"
|
||||
version = "0.13.0"
|
||||
|
22
crates/nu_plugin_start/Cargo.toml
Normal file
22
crates/nu_plugin_start/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "nu_plugin_start"
|
||||
version = "0.1.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
edition = "2018"
|
||||
description = "A plugin to open files/URLs directly from Nushell"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.13.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.13.0" }
|
||||
nu-source = { path = "../nu-source", version = "0.13.0" }
|
||||
nu-errors = { path = "../nu-errors", version = "0.13.0" }
|
||||
ansi_term = "0.12.1"
|
||||
url = "2.1.1"
|
||||
open = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = { version = "0.13.0", path = "../nu-build" }
|
3
crates/nu_plugin_start/build.rs
Normal file
3
crates/nu_plugin_start/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
nu_build::build()
|
||||
}
|
4
crates/nu_plugin_start/src/lib.rs
Normal file
4
crates/nu_plugin_start/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod nu;
|
||||
mod start;
|
||||
|
||||
pub use start::Start;
|
9
crates/nu_plugin_start/src/main.rs
Normal file
9
crates/nu_plugin_start/src/main.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_start::Start;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut Start {
|
||||
filenames: vec![],
|
||||
application: None,
|
||||
});
|
||||
}
|
25
crates/nu_plugin_start/src/nu/mod.rs
Normal file
25
crates/nu_plugin_start/src/nu/mod.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
|
||||
|
||||
use crate::start::Start;
|
||||
|
||||
impl Plugin for Start {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("start")
|
||||
.desc("Opens each file/directory/URL using the default application")
|
||||
.rest(SyntaxShape::String, "files/urls/directories to open")
|
||||
.named(
|
||||
"application",
|
||||
SyntaxShape::String,
|
||||
"Specifies the application used for opening the files/directories/urls",
|
||||
Some('a'),
|
||||
))
|
||||
}
|
||||
fn sink(&mut self, call_info: CallInfo, input: Vec<Value>) {
|
||||
self.parse(call_info, input);
|
||||
if let Err(e) = self.exec() {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
181
crates/nu_plugin_start/src/start.rs
Normal file
181
crates/nu_plugin_start/src/start.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use ansi_term::Color;
|
||||
use nu_protocol::{CallInfo, Value};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub struct Start {
|
||||
pub filenames: Vec<String>,
|
||||
pub application: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StartError {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl StartError {
|
||||
fn new(msg: &str) -> StartError {
|
||||
StartError {
|
||||
msg: msg.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StartError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: {}",
|
||||
Color::Red.bold().paint("start error"),
|
||||
self.msg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for StartError {}
|
||||
|
||||
impl Start {
|
||||
pub fn parse(&mut self, call_info: CallInfo, input: Vec<Value>) {
|
||||
input.iter().for_each(|val| {
|
||||
if val.is_some() {
|
||||
self.parse_value(val);
|
||||
}
|
||||
});
|
||||
self.parse_filenames(&call_info);
|
||||
self.parse_application(&call_info);
|
||||
}
|
||||
|
||||
fn add_filename(&mut self, filename: String) {
|
||||
if Path::new(&filename).exists() || url::Url::parse(&filename).is_ok() {
|
||||
self.filenames.push(filename);
|
||||
} else {
|
||||
print_warning(format!(
|
||||
"The file '{}' does not exist",
|
||||
Color::White.bold().paint(filename)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_filenames(&mut self, call_info: &CallInfo) {
|
||||
let candidates = match &call_info.args.positional {
|
||||
Some(values) => values
|
||||
.iter()
|
||||
.map(|val| val.as_string())
|
||||
.collect::<Result<Vec<String>, _>>()
|
||||
.unwrap_or_else(|_| vec![]),
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
for candidate in candidates {
|
||||
self.add_filename(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_application(&mut self, call_info: &CallInfo) {
|
||||
self.application = if let Some(app) = call_info.args.get("application") {
|
||||
match app.as_string() {
|
||||
Ok(name) => Some(name),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parse_value(&mut self, input: &Value) {
|
||||
if let Ok(filename) = input.as_string() {
|
||||
self.add_filename(filename);
|
||||
} else {
|
||||
print_warning(format!("Could not convert '{:?}' to string", input));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn exec(&mut self) -> Result<(), StartError> {
|
||||
let mut args = vec![];
|
||||
args.append(&mut self.filenames);
|
||||
|
||||
if let Some(app_name) = &self.application {
|
||||
args.append(&mut vec![String::from("-a"), app_name.to_string()]);
|
||||
}
|
||||
exec_cmd("open", &args)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn exec(&mut self) -> Result<(), StartError> {
|
||||
if let Some(app_name) = &self.application {
|
||||
for file in &self.filenames {
|
||||
match open::with(file, app_name) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => {
|
||||
return Err(StartError::new(
|
||||
"Failed to open file with specified application",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for file in &self.filenames {
|
||||
match open::that(file) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => {
|
||||
return Err(StartError::new(
|
||||
"Failed to open file with default application",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
pub fn exec(&mut self) -> Result<(), StartError> {
|
||||
let mut args = vec![];
|
||||
args.append(&mut self.filenames);
|
||||
|
||||
if let Some(app_name) = &self.application {
|
||||
exec_cmd(&app_name, &args)
|
||||
} else {
|
||||
for cmd in &["xdg-open", "gnome-open", "kde-open", "wslview"] {
|
||||
if exec_cmd(cmd, &args).is_err() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(StartError::new(
|
||||
"Failed to open file(s) with xdg-open. gnome-open, kde-open, and wslview",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_warning(msg: String) {
|
||||
println!("{}: {}", Color::Yellow.bold().paint("warning"), msg);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn exec_cmd(cmd: &str, args: &[String]) -> Result<(), StartError> {
|
||||
if args.is_empty() {
|
||||
return Err(StartError::new("No file(s) or application provided"));
|
||||
}
|
||||
let status = match Command::new(cmd)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.args(args)
|
||||
.status()
|
||||
{
|
||||
Ok(exit_code) => exit_code,
|
||||
Err(_) => return Err(StartError::new("Failed to run native open syscall")),
|
||||
};
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(StartError::new(
|
||||
"Failed to run start. Hint: The file(s)/application may not exist",
|
||||
))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user