Implement subcommand -m / --pipe-msg-in

pull/522/head
Arijit Basu 2 years ago committed by Arijit Basu
parent bffe1d43ec
commit 1e820030a0

5
Cargo.lock generated

@ -933,9 +933,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.86"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
dependencies = [
"itoa",
"ryu",
@ -1328,6 +1328,7 @@ dependencies = [
"natord",
"regex",
"serde",
"serde_json",
"serde_yaml",
"tui",
"tui-input",

@ -33,6 +33,7 @@ ansi-to-tui = "2.0.0"
regex = "1.6.0"
gethostname = "0.3.0"
fuzzy-matcher = "0.3.7"
serde_json = "1.0.87"
[dependencies.lazy_static]
version = "1.4.0"

@ -44,7 +44,7 @@ called ["pipe"s][18].
The other variables are single-line variables containing simple information:
- [XPLR][38]
- [XPLR][30]
- [XPLR_APP_VERSION][30]
- [XPLR_FOCUS_INDEX][31]
- [XPLR_FOCUS_PATH][32]

@ -57,9 +57,8 @@ Finally, after publishing, don't hesitate to
- When executing commands, prefer `Call0` over `Call`, `BashExec0` over
`BashExec` and so on. File names may contain newline characters
(e.g. `foo$'\n'bar`).
- File names may also contain quotes. Use the syntax
`printf 'Call0: "%s"' "${FOO_ESC}"` where `FOO_ESC=${FOO//\"/\\\"}` and
`FOO_ESC=${FOO_ESC//$'\n'/\\n}`.
- File names may also contain quotes. Avoid writing directly to
`$XPLR_PIPE_MSG_IN`. Use `xplr -m` / `xplr --pipe-msg-in` instead.
- Check for empty variables using the syntax `${FOO:?}` or use a default value
`${FOO:-defaultvalue}`.

@ -161,6 +161,8 @@ pub struct InputBuffer {
#[derive(Debug, Serialize, Deserialize)]
pub struct App {
pub bin: String,
pub version: String,
#[serde(with = "serde_yaml::with::singleton_map_recursive")]
@ -187,6 +189,7 @@ pub struct App {
impl App {
pub fn create(
bin: String,
pwd: PathBuf,
lua: &mlua::Lua,
config_file: Option<PathBuf>,
@ -298,6 +301,7 @@ impl App {
};
let mut app = Self {
bin,
version: VERSION.to_string(),
config,
pwd,
@ -1664,7 +1668,7 @@ impl App {
pub fn write_pipes(&self, delimiter: char) -> Result<()> {
fs::create_dir_all(self.pipe.path.clone())?;
fs::write(&self.pipe.msg_in, "")?;
fs::write(&self.pipe.msg_in, &[delimiter as u8])?;
let selection_str = self.selection_str(delimiter);
fs::write(&self.pipe.selection_out, selection_str)?;

@ -2,7 +2,7 @@
use std::env;
use xplr::cli::Cli;
use xplr::cli::{self, Cli};
use xplr::runner;
fn main() {
@ -20,6 +20,7 @@ fn main() {
-- Denotes the end of command-line flags and options
--force-focus Focuses on the given <PATH>, even if it is a directory
-h, --help Prints help information
-m, --pipe-msg-in Helps passing messages to the active xplr session
--print-pwd-as-result Prints the present working directory when quitting
with `PrintResultAndQuit`
--read-only Enables read-only mode (config.general.read_only)
@ -53,6 +54,11 @@ fn main() {
println!("{}", help);
} else if cli.version {
println!("xplr {}", xplr::app::VERSION);
} else if !cli.pipe_msg_in.is_empty() {
if let Err(err) = cli::pipe_msg_in(cli.pipe_msg_in) {
eprintln!("error: {}", err);
std::process::exit(1);
}
} else {
match runner::from_cli(cli).and_then(|a| a.run()) {
Ok(Some(out)) => {

@ -1,13 +1,15 @@
use anyhow::{bail, Result};
use std::env;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use crate::app;
use anyhow::{bail, Context, Result};
use serde_json as json;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
use std::{env, fs};
/// The arguments to pass
#[derive(Debug, Clone, Default)]
pub struct Cli {
pub bin: String,
pub version: bool,
pub help: bool,
pub read_only: bool,
@ -18,6 +20,7 @@ pub struct Cli {
pub config: Option<PathBuf>,
pub extra_config: Vec<PathBuf>,
pub on_load: Vec<app::ExternalMsg>,
pub pipe_msg_in: Vec<String>,
pub paths: Vec<PathBuf>,
}
@ -38,8 +41,9 @@ impl Cli {
/// Parse arguments from the command-line
pub fn parse(args: env::Args) -> Result<Self> {
let mut args = args.skip(1).peekable();
let mut cli = Self::default();
let mut args = args.peekable();
cli.bin = args.next().context("failed to parse xplr binary path")?;
let mut flag_ends = false;
@ -114,6 +118,12 @@ impl Cli {
cli.print_pwd_as_result = true;
}
"-m" | "--pipe-msg-in" => {
while let Some(arg) = args.next_if(|arg| !arg.starts_with('-')) {
cli.pipe_msg_in.push(arg);
}
}
// path
path => {
cli.read_path(path)?;
@ -124,3 +134,66 @@ impl Cli {
Ok(cli)
}
}
pub fn pipe_msg_in(args: Vec<String>) -> Result<()> {
let mut args = args.into_iter();
let format = args.next().context("usage: xplr -m FORMAT [ARGUMENT]...")?;
let mut msg = "".to_string();
let mut last_char = None;
let chars = format.chars();
for ch in chars {
match (ch, last_char) {
('%', Some('%')) => {
msg.push(ch);
last_char = None;
}
('%', _) => {
last_char = Some(ch);
}
('q', Some('%')) => {
let arg = args.next().context("not enough arguments")?;
msg.push_str(&json::to_string(&arg)?);
last_char = None;
}
('s', Some('%')) => {
let arg = args.next().context("not enough arguments")?;
msg.push_str(&arg);
last_char = None;
}
(ch, Some('%')) => {
bail!(format!("invalid placeholder: %{}, use %s, %q or %%", ch));
}
(ch, _) => {
msg.push(ch);
last_char = Some(ch);
}
}
}
if last_char == Some('%') {
bail!("message ended with incomplete placeholder");
}
if args.count() != 0 {
bail!("too many arguments")
}
let path = std::env::var("XPLR_PIPE_MSG_IN")
.context("passing messages in only works inside xplr shell")?;
let delimiter = fs::read(&path)?
.first()
.cloned()
.context("failed to detect delimmiter")?;
msg.push(delimiter.try_into()?);
File::options()
.append(true)
.open(&path)?
.write(msg.as_bytes())?;
Ok(())
}

@ -1156,9 +1156,7 @@ xplr.config.modes.builtin.default = {
{
BashExecSilently0 = [===[
NAME=$(basename "${XPLR_FOCUS_PATH:?}")
NAME_ESC=${NAME//\"/\\\"}
NAME_ESC=${NAME_ESC//$'\n'/\\n}
printf 'SetInputBuffer: "%s"\0' "${NAME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'SetInputBuffer: %q' "${NAME:?}"
]===],
},
},
@ -1171,9 +1169,7 @@ xplr.config.modes.builtin.default = {
{
BashExecSilently0 = [===[
NAME=$(basename "${XPLR_FOCUS_PATH:?}")
NAME_ESC=${NAME//\"/\\\"}
NAME_ESC=${NAME_ESC//$'\n'/\\n}
printf 'SetInputBuffer: "%s"\0' "${NAME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'SetInputBuffer: %q' "${NAME:?}"
]===],
},
},
@ -1209,9 +1205,7 @@ xplr.config.modes.builtin.default = {
messages = {
{
BashExecSilently0 = [===[
HOME_ESC=${HOME//\"/\\\"}
HOME_ESC=${HOME_ESC//$'\n'/\\n}
printf 'ChangeDirectory: "%s"\0' "${HOME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'ChangeDirectory: %q' "${HOME:?}"
]===],
},
},
@ -1370,14 +1364,12 @@ xplr.config.modes.builtin.go_to_path = {
{
BashExecSilently0 = [===[
PTH=${XPLR_INPUT_BUFFER}
PTH_ESC=${PTH//\"/\\\"}
PTH_ESC=${PTH_ESC//$'\n'/\\n}
if [ -d "$PTH" ]; then
printf 'ChangeDirectory: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'ChangeDirectory: %q' "$PTH"
elif [ -e "$PTH" ]; then
printf 'FocusPath: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'FocusPath: %q' "$PTH"
else
printf 'LogError: "Could not find %s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "could not find $PTH"
fi
]===],
},
@ -1412,16 +1404,14 @@ xplr.config.modes.builtin.selection_ops = {
{
BashExec0 = [===[
(while IFS= read -r -d '' LINE; do
LINE_ESC=${LINE//\"/\\\"}
LINE_ESC=${LINE_ESC//$'\n'/\\n}
if cp -vr -- "${LINE:?}" ./; then
printf 'LogSuccess: "%s"\0' "$LINE_ESC copied to ." >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogSuccess: %q' "$LINE copied to ."
else
printf 'LogError: "%s"\0' "Failed to copy $LINE_ESC to ." >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "Failed to copy $LINE to ."
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}"
printf 'ClearSelection\0' >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m ExplorePwdAsync
"$XPLR" -m ClearSelection
read -p "[enter to continue]"
]===],
},
@ -1434,15 +1424,13 @@ xplr.config.modes.builtin.selection_ops = {
{
BashExec0 = [===[
(while IFS= read -r -d '' LINE; do
LINE_ESC=${LINE//\"/\\\"}
LINE_ESC=${LINE_ESC//$'\n'/\\n}
if mv -v -- "${LINE:?}" ./; then
printf 'LogSuccess: "%s"\0' "$LINE_ESC moved to ." >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogSuccess: %q' "$LINE moved to ."
else
printf 'LogError: "%s"\0' "Failed to move $LINE_ESC to ." >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "Failed to move $LINE to ."
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m ExplorePwdAsync
read -p "[enter to continue]"
]===],
},
@ -1507,16 +1495,14 @@ xplr.config.modes.builtin.create_directory = {
{
BashExecSilently0 = [===[
PTH="$XPLR_INPUT_BUFFER"
PTH_ESC=${PTH//\"/\\\"}
PTH_ESC=${PTH_ESC//$'\n'/\\n}
if [ "$PTH" ]; then
mkdir -p -- "$PTH" \
&& printf 'SetInputBuffer: ""\0' >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'LogSuccess: "%s"\0' "$PTH_ESC created" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'FocusPath: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
&& "$XPLR" -m 'SetInputBuffer: ""' \
&& "$XPLR" -m ExplorePwd \
&& "$XPLR" -m 'LogSuccess: %q' "$PTH created" \
&& "$XPLR" -m 'FocusPath: %q' "$PTH"
else
printf 'PopMode\0' >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m PopMode
fi
]===],
},
@ -1551,17 +1537,15 @@ xplr.config.modes.builtin.create_file = {
{
BashExecSilently0 = [===[
PTH="$XPLR_INPUT_BUFFER"
PTH_ESC=${PTH//\"/\\\"}
PTH_ESC=${PTH_ESC//$'\n'/\\n}
if [ "$PTH" ]; then
mkdir -p -- "$(dirname $PTH)" \
&& touch -- "$PTH" \
&& printf 'SetInputBuffer: ""\0' >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'LogSuccess: "%s"\0' "$PTH_ESC created" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'FocusPath: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
&& "$XPLR" -m 'SetInputBuffer: ""' \
&& "$XPLR" -m 'LogSuccess: %q' "$PTH created" \
&& "$XPLR" -m 'ExplorePwd' \
&& "$XPLR" -m 'FocusPath: %q' "$PTH"
else
printf 'PopMode\0' >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m PopMode
fi
]===],
},
@ -1664,7 +1648,7 @@ xplr.config.modes.builtin.go_to = {
elif command -v open; then
OPENER=open
else
printf 'LogError: "$OPENER not found"\0' >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: "$OPENER not found"'
exit 1
fi
fi
@ -1700,18 +1684,14 @@ xplr.config.modes.builtin.rename = {
{
BashExecSilently0 = [===[
SRC="${XPLR_FOCUS_PATH:?}"
SRC_ESC=${SRC//\"/\\\"}
SRC_ESC=${SRC_ESC//$'\n'/\\n}
TARGET="${XPLR_INPUT_BUFFER:?}"
TARGET_ESC=${TARGET//\"/\\\"}
TARGET_ESC=${TARGET_ESC//$'\n'/\\n}
if [ -e "${TARGET:?}" ]; then
printf 'LogError: "%s"\0' "$TARGET_ESC already exists" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "$TARGET already exists"
else
mv -- "${SRC:?}" "${TARGET:?}" \
&& printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'FocusPath: "%s"\0' "$TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'LogSuccess: "%s"\0' "$SRC_ESC renamed to $TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
&& "$XPLR" -m ExplorePwd \
&& "$XPLR" -m 'FocusPath: %q' "$TARGET" \
&& "$XPLR" -m 'LogSuccess: %q' "$SRC renamed to $TARGET"
fi
]===],
},
@ -1746,18 +1726,14 @@ xplr.config.modes.builtin.duplicate_as = {
{
BashExecSilently0 = [===[
SRC="${XPLR_FOCUS_PATH:?}"
SRC_ESC=${SRC//\"/\\\"}
SRC_ESC=${SRC_ESC//$'\n'/\\n}
TARGET="${XPLR_INPUT_BUFFER:?}"
TARGET_ESC=${TARGET//\"/\\\"}
TARGET_ESC=${TARGET_ESC//$'\n'/\\n}
if [ -e "${TARGET:?}" ]; then
printf 'LogError: "%s"\0' "$TARGET_ESC already exists" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "$TARGET already exists"
else
cp -r -- "${SRC:?}" "${TARGET:?}" \
&& printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'FocusPath: "%s"\0' "$TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf 'LogSuccess: "%s"\0' "$SRC_ESC duplicated as $TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
&& "$XPLR" -m ExplorePwd \
&& "$XPLR" -m 'FocusPath: %q' "$TARGET" \
&& "$XPLR" -m 'LogSuccess: %q' "$SRC duplicated as $TARGET"
fi
]===],
},
@ -1786,15 +1762,13 @@ xplr.config.modes.builtin.delete = {
{
BashExec0 = [===[
(while IFS= read -r -d '' LINE; do
LINE_ESC=${LINE//\"/\\\"}
LINE_ESC=${LINE_ESC//$'\n'/\\n}
if rm -rfv -- "${LINE:?}"; then
printf 'LogSuccess: "%s"\0' "$LINE_ESC deleted" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogSuccess: %q' "$LINE deleted"
else
printf 'LogError: "%s"\0' "Failed to delete $LINE_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "Failed to delete $LINE"
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m ExplorePwdAsync
read -p "[enter to continue]"
]===],
},
@ -1807,23 +1781,21 @@ xplr.config.modes.builtin.delete = {
{
BashExec0 = [===[
(while IFS= read -r -d '' LINE; do
LINE_ESC=${LINE//\"/\\\"}
LINE_ESC=${LINE_ESC//$'\n'/\\n}
if [ -d "$LINE" ] && [ ! -L "$LINE" ]; then
if rmdir -v -- "${LINE:?}"; then
printf 'LogSuccess: "%s"\0' "$LINE_ESC deleted" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogSuccess: %q' "$LINE deleted"
else
printf 'LogError: "%s"\0' "Failed to delete $LINE_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "Failed to delete $LINE"
fi
else
if rm -v -- "${LINE:?}"; then
printf 'LogSuccess: "%s"\0' "$LINE_ESC deleted" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogSuccess: %q' "$LINE deleted"
else
printf 'LogError: "%s"\0' "Failed to delete $LINE_ESC" >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m 'LogError: %q' "Failed to delete $LINE"
fi
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}"
"$XPLR" -m ExplorePwdAsync
read -p "[enter to continue]"
]===],
},

@ -54,7 +54,7 @@ impl Pipe {
}
}
pub fn read_all(pipe: &str) -> Result<Vec<ExternalMsg>> {
pub fn read_all(pipe: &str, delimiter: char) -> Result<Vec<ExternalMsg>> {
let mut file = fs::OpenOptions::new()
.read(true)
.write(true)
@ -65,12 +65,12 @@ pub fn read_all(pipe: &str) -> Result<Vec<ExternalMsg>> {
file.read_to_string(&mut in_str)?;
file.set_len(0)?;
let delim = '\0';
if !in_str.is_empty() {
let mut msgs = vec![];
for msg in in_str.trim_matches(delim).split(delim) {
msgs.push(msg.try_into()?);
for msg in in_str.trim_matches(delimiter).split(delimiter) {
if !msg.is_empty() {
msgs.push(msg.try_into()?);
}
}
Ok(msgs)
} else {

@ -107,7 +107,8 @@ fn call(
.unwrap_or_default();
let status = Command::new(cmd.command.clone())
.env("XPLR_APP_VERSION", app.version.clone())
.env("XPLR", &app.bin)
.env("XPLR_APP_VERSION", &app.version)
.env("XPLR_PID", &app.pid.to_string())
.env("XPLR_INPUT_BUFFER", input_buffer)
.env("XPLR_FOCUS_PATH", app.focused_node_str())
@ -188,6 +189,7 @@ fn start_fifo(path: &str, focus_path: &str) -> Result<fs::File> {
}
pub struct Runner {
bin: String,
pwd: PathBuf,
focused_path: Option<PathBuf>,
config_file: Option<PathBuf>,
@ -225,6 +227,7 @@ impl Runner {
}
Ok(Self {
bin: cli.bin,
pwd,
focused_path,
config_file: cli.config,
@ -241,8 +244,13 @@ impl Runner {
pub fn run(self) -> Result<Option<String>> {
// Why unsafe? See https://github.com/sayanarijit/xplr/issues/309
let lua = unsafe { mlua::Lua::unsafe_new() };
let mut app =
app::App::create(self.pwd, &lua, self.config_file, self.extra_config_files)?;
let mut app = app::App::create(
self.bin,
self.pwd,
&lua,
self.config_file,
self.extra_config_files,
)?;
app.config.general.read_only = self.read_only;
fs::create_dir_all(app.session_path.clone())?;

Loading…
Cancel
Save