mirror of
https://github.com/quantum5/ntfy-run.git
synced 2025-04-24 13:41:58 -04:00
Compare commits
No commits in common. "2109cd45aa7b6d44dbb6238104968702560e6b32" and "765f3e6bd305f0a1df34cdb3c2f70fe88ff70a65" have entirely different histories.
2109cd45aa
...
765f3e6bd3
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -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"] }
|
||||||
|
|
|
@ -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
|
||||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue