feat: noil now handles open, and open in non-terminals via. /dev/tty
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
91
Cargo.lock
generated
91
Cargo.lock
generated
@@ -257,12 +257,44 @@ version = "0.1.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenvy"
|
||||||
version = "0.15.7"
|
version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -345,6 +377,16 @@ version = "0.2.174"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@@ -399,6 +441,7 @@ dependencies = [
|
|||||||
"ignore",
|
"ignore",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"rand",
|
"rand",
|
||||||
|
"shellexpand",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -436,6 +479,12 @@ version = "1.70.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -540,7 +589,7 @@ version = "0.9.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -552,6 +601,17 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"libredox",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -619,6 +679,15 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellexpand"
|
||||||
|
version = "3.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -673,6 +742,26 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
|
@@ -21,6 +21,7 @@ ignore = "0.4.23"
|
|||||||
blake3 = "1.8.2"
|
blake3 = "1.8.2"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
|
shellexpand = "3.1.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
|
@@ -34,6 +34,7 @@ impl ApplyCommand {
|
|||||||
&original,
|
&original,
|
||||||
ApplyOptions {
|
ApplyOptions {
|
||||||
chooser_file: self.chooser_file.clone(),
|
chooser_file: self.chooser_file.clone(),
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -49,6 +50,7 @@ impl ApplyCommand {
|
|||||||
&input,
|
&input,
|
||||||
ApplyOptions {
|
ApplyOptions {
|
||||||
chooser_file: self.chooser_file.clone(),
|
chooser_file: self.chooser_file.clone(),
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
use std::{
|
use std::{
|
||||||
env::temp_dir,
|
env::temp_dir,
|
||||||
io::Write,
|
io::{IsTerminal, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
process::Stdio,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ansi_term::Color;
|
use ansi_term::Color;
|
||||||
use anyhow::{Context, bail};
|
use anyhow::{Context, bail};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::{
|
||||||
|
fs::OpenOptions,
|
||||||
|
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commit::{Action, print_changes},
|
commit::{Action, print_changes},
|
||||||
@@ -26,6 +30,12 @@ pub struct EditCommand {
|
|||||||
|
|
||||||
#[arg(long = "chooser-file", env = "NOIL_CHOOSER_FILE")]
|
#[arg(long = "chooser-file", env = "NOIL_CHOOSER_FILE")]
|
||||||
chooser_file: Option<PathBuf>,
|
chooser_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long = "commit")]
|
||||||
|
commit: bool,
|
||||||
|
|
||||||
|
#[arg(long = "quiet")]
|
||||||
|
quiet: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditCommand {
|
impl EditCommand {
|
||||||
@@ -52,9 +62,13 @@ impl EditCommand {
|
|||||||
.await
|
.await
|
||||||
.context("create temp file for noil")?;
|
.context("create temp file for noil")?;
|
||||||
|
|
||||||
let output = get_outputs(&self.path, true).await?;
|
let output = get_outputs(&self.get_path().await.context("get path")?, true)
|
||||||
file.write_all(output.as_bytes()).await?;
|
.await
|
||||||
file.flush().await?;
|
.context("get output")?;
|
||||||
|
file.write_all(output.as_bytes())
|
||||||
|
.await
|
||||||
|
.context("write contents for edit")?;
|
||||||
|
file.flush().await.context("flush contents for edit")?;
|
||||||
|
|
||||||
let editor = std::env::var("EDITOR").context("EDITOR not found in env")?;
|
let editor = std::env::var("EDITOR").context("EDITOR not found in env")?;
|
||||||
|
|
||||||
@@ -62,7 +76,22 @@ impl EditCommand {
|
|||||||
let mut cmd = tokio::process::Command::new(editor.trim());
|
let mut cmd = tokio::process::Command::new(editor.trim());
|
||||||
cmd.arg(&file_path);
|
cmd.arg(&file_path);
|
||||||
|
|
||||||
let mut process = cmd.spawn()?;
|
if !std::io::stdout().is_terminal() {
|
||||||
|
let tty = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open("/dev/tty")
|
||||||
|
.await
|
||||||
|
.context("open tty")?;
|
||||||
|
let tty_in = tty.try_clone().await.context("clone ttyin")?;
|
||||||
|
let tty_out = tty.try_clone().await.context("clone ttyout")?;
|
||||||
|
|
||||||
|
cmd.stdin(Stdio::from(tty_in.into_std().await))
|
||||||
|
.stdout(Stdio::from(tty_out.into_std().await))
|
||||||
|
.stderr(Stdio::from(tty.into_std().await));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut process = cmd.spawn().context("command not found")?;
|
||||||
let status = process.wait().await.context("editor closed prematurely")?;
|
let status = process.wait().await.context("editor closed prematurely")?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
let code = status.code().unwrap_or(-1);
|
let code = status.code().unwrap_or(-1);
|
||||||
@@ -73,7 +102,13 @@ impl EditCommand {
|
|||||||
.await
|
.await
|
||||||
.context("read noil file")?;
|
.context("read noil file")?;
|
||||||
|
|
||||||
let res = print_changes(&noil_content, PREVIEW).await;
|
let res = if !self.commit {
|
||||||
|
print_changes(&noil_content, PREVIEW).await
|
||||||
|
} else {
|
||||||
|
Ok(Action::Apply {
|
||||||
|
original: noil_content,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let action = match res {
|
let action = match res {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
@@ -83,7 +118,7 @@ impl EditCommand {
|
|||||||
Color::Red.normal().paint(format!("{e:?}"))
|
Color::Red.normal().paint(format!("{e:?}"))
|
||||||
);
|
);
|
||||||
|
|
||||||
wait_user().await?;
|
wait_user().await.context("user finished prematurely")?;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -96,6 +131,7 @@ impl EditCommand {
|
|||||||
&original,
|
&original,
|
||||||
ApplyOptions {
|
ApplyOptions {
|
||||||
chooser_file: self.chooser_file.clone(),
|
chooser_file: self.chooser_file.clone(),
|
||||||
|
quiet: self.quiet,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -104,6 +140,25 @@ impl EditCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_path(&self) -> anyhow::Result<PathBuf> {
|
||||||
|
let path_str = self.path.display().to_string();
|
||||||
|
let expanded_path = shellexpand::full(&path_str)?;
|
||||||
|
let path = PathBuf::from(expanded_path.to_string());
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
anyhow::bail!("path: {} does not exist", self.path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.is_file() {
|
||||||
|
return path
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.to_path_buf())
|
||||||
|
.ok_or(anyhow::anyhow!("parent doesn't exist for file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(path.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_user() -> Result<(), anyhow::Error> {
|
async fn wait_user() -> Result<(), anyhow::Error> {
|
||||||
@@ -112,13 +167,17 @@ async fn wait_user() -> Result<(), anyhow::Error> {
|
|||||||
let stdin = tokio::io::stdin();
|
let stdin = tokio::io::stdin();
|
||||||
let mut reader = BufReader::new(stdin);
|
let mut reader = BufReader::new(stdin);
|
||||||
let mut input_buf = String::new();
|
let mut input_buf = String::new();
|
||||||
reader.read_line(&mut input_buf).await?;
|
reader
|
||||||
|
.read_line(&mut input_buf)
|
||||||
|
.await
|
||||||
|
.context("failed to read stdin")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct ApplyOptions {
|
pub struct ApplyOptions {
|
||||||
pub chooser_file: Option<PathBuf>,
|
pub chooser_file: Option<PathBuf>,
|
||||||
|
pub quiet: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the philosphy behind apply is that we try unlike normal file system operations to be idempotent.
|
/// the philosphy behind apply is that we try unlike normal file system operations to be idempotent.
|
||||||
@@ -129,7 +188,9 @@ pub struct ApplyOptions {
|
|||||||
///
|
///
|
||||||
/// All in all apply is mostly idempotent, and won't override files, it tries to be as non destructive as possible. For example move will only throw a warning if the source file doesn't exists, but the destination does
|
/// All in all apply is mostly idempotent, and won't override files, it tries to be as non destructive as possible. For example move will only throw a warning if the source file doesn't exists, but the destination does
|
||||||
pub async fn apply(input: &str, options: ApplyOptions) -> anyhow::Result<()> {
|
pub async fn apply(input: &str, options: ApplyOptions) -> anyhow::Result<()> {
|
||||||
|
if !options.quiet {
|
||||||
eprintln!("applying changes");
|
eprintln!("applying changes");
|
||||||
|
}
|
||||||
|
|
||||||
let noil_index = parse::parse_input(input).context("parse input")?;
|
let noil_index = parse::parse_input(input).context("parse input")?;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user