Compare commits

..

No commits in common. "2109cd45aa7b6d44dbb6238104968702560e6b32" and "765f3e6bd305f0a1df34cdb3c2f70fe88ff70a65" have entirely different histories.

6 changed files with 36 additions and 50 deletions

2
Cargo.lock generated
View file

@ -592,7 +592,7 @@ dependencies = [
[[package]] [[package]]
name = "ntfy-run" name = "ntfy-run"
version = "0.1.2" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"itertools", "itertools",

View file

@ -1,19 +1,7 @@
[package] [package]
name = "ntfy-run" name = "ntfy-run"
version = "0.1.2" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Quantum <me@quantum5.ca>"]
description = "ntfy-run is a tool to run a command, capture its output, and send it to a ntfy server."
readme = "README.md"
homepage = "https://github.com/quantum5/ntfy-run"
repository = "https://github.com/quantum5/ntfy-run"
license = "GPL-3.0-or-later"
keywords = ["ntfy", "cron", "notifications", "utility"]
categories = ["command-line-interface"]
exclude = [
".github/*"
]
[dependencies] [dependencies]
clap = { version = "4.5.20", features = ["derive", "env"] } clap = { version = "4.5.20", features = ["derive", "env"] }

View file

@ -16,13 +16,7 @@ sudo wget https://github.com/quantum5/ntfy-run/releases/latest/download/ntfy-run
sudo chmod a+x /usr/local/bin/ntfy-run sudo chmod a+x /usr/local/bin/ntfy-run
``` ```
You can also install the latest release with `cargo`: Alternatively, build it yourself:
```bash
cargo install ntfy-run
```
Finally, you can build the latest Git version yourself:
```bash ```bash
git clone https://github.com/quantum5/ntfy-run.git git clone https://github.com/quantum5/ntfy-run.git

View file

@ -1,13 +1,13 @@
use crate::runner::{CaptureError, CapturedOutput}; use crate::runner::CaptureError;
use clap::Parser; use clap::Parser;
use std::process::exit; use runner::CapturedOutput;
mod quote; mod quote;
mod runner; mod runner;
mod tap_stream; mod tap_stream;
#[derive(Parser)] #[derive(Parser)]
#[command(version, about)] /// Tool to run a command, capture its output, and send it to ntfy.
struct Cli { struct Cli {
/// URL of the ntfy server and topic, e.g. https://ntfy.sh/topic /// URL of the ntfy server and topic, e.g. https://ntfy.sh/topic
#[arg(short = 'n', long = "ntfy-url", env = "NTFY_URL", alias = "url")] #[arg(short = 'n', long = "ntfy-url", env = "NTFY_URL", alias = "url")]
@ -100,8 +100,7 @@ fn format_post_body(output: CapturedOutput) -> String {
}]; }];
if !output.errors.is_empty() { if !output.errors.is_empty() {
fragments.push("".to_string()); fragments.push("==================== Errors ====================".to_string());
fragments.push("========== Errors ==========".to_string());
for error in &output.errors { for error in &output.errors {
fragments.push(match error { fragments.push(match error {
CaptureError::Spawn(error) => format!("Spawn error: {}", error), CaptureError::Spawn(error) => format!("Spawn error: {}", error),
@ -110,18 +109,19 @@ fn format_post_body(output: CapturedOutput) -> String {
CaptureError::Wait(error) => format!("Error while waiting for process: {}", error), CaptureError::Wait(error) => format!("Error while waiting for process: {}", error),
}); });
} }
fragments.push("\n".to_string());
} }
if !output.stdout.is_empty() { if !output.stdout.is_empty() {
fragments.push("".to_string()); fragments.push("==================== STDOUT ====================".to_string());
fragments.push("========== STDOUT ==========".to_string()); fragments.push(String::from_utf8_lossy(&output.stdout).into_owned());
fragments.push(String::from_utf8_lossy(output.stdout.trim_ascii_end()).into_owned()); fragments.push("\n".to_string());
} }
if !output.stderr.is_empty() { if !output.stderr.is_empty() {
fragments.push("".to_string()); fragments.push("==================== STDERR ====================".to_string());
fragments.push("========== STDERR ==========".to_string()); fragments.push(String::from_utf8_lossy(&output.stderr).into_owned());
fragments.push(String::from_utf8_lossy(output.stderr.trim_ascii_end()).into_owned()); fragments.push("\n".to_string());
} }
fragments.join("\n") fragments.join("\n")
@ -210,13 +210,7 @@ async fn main() {
}; };
match request.send().await.and_then(|r| r.error_for_status()) { match request.send().await.and_then(|r| r.error_for_status()) {
Ok(_) => exit(match status { Ok(_) => (),
Some(code) => code.code().unwrap_or(255), Err(error) => eprintln!("Failed to send request to ntfy: {}", error),
None => 255,
}),
Err(error) => {
eprintln!("Failed to send request to ntfy: {}", error);
exit(37)
}
} }
} }

View file

@ -1,6 +1,7 @@
use crate::tap_stream::{ReadOrWrite, TapStream}; use crate::tap_stream::{ReadOrWrite, TapStream};
use std::process::{ExitStatus, Stdio}; use std::process::{ExitStatus, Stdio};
use tokio::{io, process::Command, select}; use tokio::process::Command;
use tokio::{io, select};
pub enum CaptureError { pub enum CaptureError {
Spawn(io::Error), Spawn(io::Error),
@ -60,20 +61,29 @@ pub async fn run_forward_and_capture(cmdline: &Vec<String>) -> CapturedOutput {
result = stdout_tap.step(), if !stdout_eof => match result { result = stdout_tap.step(), if !stdout_eof => match result {
Ok(ReadOrWrite::Read(bytes)) => stdout.extend_from_slice(bytes), Ok(ReadOrWrite::Read(bytes)) => stdout.extend_from_slice(bytes),
Ok(ReadOrWrite::Written) => (), Ok(ReadOrWrite::Written) => (),
Ok(ReadOrWrite::EOF) => stdout_eof = true, Ok(ReadOrWrite::EOF) => match (stderr_eof, maybe_status) {
(true, Some(status)) => break status,
_ => stdout_eof = true,
},
Err(error) => errors.push(CaptureError::Stdout(error)), Err(error) => errors.push(CaptureError::Stdout(error)),
}, },
result = stderr_tap.step(), if !stderr_eof => match result { result = stderr_tap.step(), if !stderr_eof => match result {
Ok(ReadOrWrite::Read(bytes)) => stderr.extend_from_slice(bytes), Ok(ReadOrWrite::Read(bytes)) => stderr.extend_from_slice(bytes),
Ok(ReadOrWrite::Written) => (), Ok(ReadOrWrite::Written) => (),
Ok(ReadOrWrite::EOF) => stderr_eof = true, Ok(ReadOrWrite::EOF) => match (stdout_eof, maybe_status) {
(true, Some(status)) => break status,
_ => stderr_eof = true,
},
Err(error) => errors.push(CaptureError::Stderr(error)), Err(error) => errors.push(CaptureError::Stderr(error)),
}, },
status = child.wait(), if maybe_status.is_none() => match status { status = child.wait(), if maybe_status.is_none() => match status {
Ok(status) => maybe_status = Some(status), Ok(status) => if stdout_eof && stderr_eof {
break status;
} else {
maybe_status = Some(status);
},
Err(error) => errors.push(CaptureError::Wait(error)), Err(error) => errors.push(CaptureError::Wait(error)),
}, }
else => break maybe_status.unwrap(),
} }
}; };

View file

@ -34,11 +34,11 @@ impl<R: AsyncRead + Unpin, W: AsyncWrite + Unpin> TapStream<R, W> {
self.buf_start = 0; self.buf_start = 0;
self.buf_end = bytes; self.buf_end = bytes;
if bytes == 0 { Ok(if bytes == 0 {
Ok(ReadOrWrite::EOF) ReadOrWrite::EOF
} else { } else {
Ok(ReadOrWrite::Read(&self.buffer[0..bytes])) ReadOrWrite::Read(&self.buffer[0..bytes])
} })
} else { } else {
let bytes = self let bytes = self
.target .target