Skip to content

Commit c8020a6

Browse files
committed
Add external processor support
1 parent 0641b71 commit c8020a6

File tree

6 files changed

+185
-3
lines changed

6 files changed

+185
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Allow `partition_table_offset` to be specified in the config file. (for #699)
13+
- Support external log-processors
1314

1415
### Changed
1516

cargo-espflash/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> {
368368
args.flash_args.monitor_baud.unwrap_or(default_baud),
369369
args.flash_args.log_format,
370370
true,
371+
None,
372+
None,
371373
)
372374
} else {
373375
Ok(())

espflash/src/bin/espflash.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub struct Cli {
3030
subcommand: Commands,
3131

3232
/// Do not check for updates
33-
#[clap(short, long, global = true, action)]
33+
#[clap(long, global = true, action)]
3434
skip_update_check: bool,
3535
}
3636

@@ -303,6 +303,8 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> {
303303
args.flash_args.monitor_baud.unwrap_or(default_baud),
304304
args.flash_args.log_format,
305305
true,
306+
args.flash_args.processors,
307+
Some(args.image),
306308
)
307309
} else {
308310
Ok(())

espflash/src/cli/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ pub struct FlashArgs {
156156
pub no_skip: bool,
157157
#[clap(flatten)]
158158
pub image: ImageArgs,
159+
/// External log processors to use (comma separated executables)
160+
#[arg(long, requires = "monitor")]
161+
pub processors: Option<String>,
159162
}
160163

161164
/// Operations for partitions tables
@@ -262,6 +265,9 @@ pub struct MonitorArgs {
262265
/// Logging format.
263266
#[arg(long, short = 'L', default_value = "serial", requires = "elf")]
264267
pub log_format: LogFormat,
268+
/// External log processors to use (comma separated executables)
269+
#[arg(long)]
270+
processors: Option<String>,
265271
}
266272

267273
#[derive(Debug, Args)]
@@ -418,7 +424,7 @@ pub fn serial_monitor(args: MonitorArgs, config: &Config) -> Result<()> {
418424
let mut flasher = connect(&args.connect_args, config, true, true)?;
419425
let pid = flasher.get_usb_pid()?;
420426

421-
let elf = if let Some(elf_path) = args.elf {
427+
let elf = if let Some(elf_path) = args.elf.clone() {
422428
let path = fs::canonicalize(elf_path).into_diagnostic()?;
423429
let data = fs::read(path).into_diagnostic()?;
424430

@@ -447,6 +453,8 @@ pub fn serial_monitor(args: MonitorArgs, config: &Config) -> Result<()> {
447453
args.connect_args.baud.unwrap_or(default_baud),
448454
args.log_format,
449455
!args.non_interactive,
456+
args.processors,
457+
args.elf,
450458
)
451459
}
452460

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//! External processor support
2+
//!
3+
//! Via the command line argument `--processors` you can instruct espflash to run external executables to pre-process
4+
//! the logs received from the target. Multiple processors are supported by separating them via `,`. Processors are executed in the specified order.
5+
//!
6+
//! You can use full-qualified paths or run an executable which is already in the search path.
7+
//!
8+
//! A processors reads from stdin and output to stdout. Be aware this runs before further processing by espflash.
9+
//! i.e. addresses are not resolved and when using `defmt` you will see encoded data.
10+
//!
11+
//! Additionally be aware that you might receive chunked data which is not always split at valid UTF character boundaries.
12+
//!
13+
//! The executable will get the path of the ELF file as the first argument if available.
14+
//!
15+
//! Example processor which turns some letters into uppercase
16+
//! ```rust,no-run
17+
//! use std::io::{stdin, stdout, Read, Write};
18+
//!
19+
//! fn main() {
20+
//! let args: Vec<String> = std::env::args().collect();
21+
//! println!("ELF file: {:?}", args[1]);
22+
//!
23+
//! let mut buf = [0u8; 1024];
24+
//! loop {
25+
//! if let Ok(len) = stdin().read(&mut buf) {
26+
//! for b in &mut buf[..len] {
27+
//! *b = if b"abdfeo".contains(b) {
28+
//! b.to_ascii_uppercase()
29+
//! } else {
30+
//! *b
31+
//! };
32+
//! }
33+
//!
34+
//! stdout().write(&buf[..len]).unwrap();
35+
//! stdout().flush().unwrap();
36+
//! } else {
37+
//! // ignored
38+
//! }
39+
//! }
40+
//! }
41+
//! ```
42+
43+
use std::{
44+
fmt::Display,
45+
io::{Read, Write},
46+
path::PathBuf,
47+
process::{Child, ChildStdin, Stdio},
48+
sync::mpsc,
49+
};
50+
51+
use miette::Diagnostic;
52+
53+
#[derive(Debug)]
54+
pub struct Error {
55+
executable: String,
56+
}
57+
58+
impl Display for Error {
59+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60+
write!(f, "Failed to launch '{}'", self.executable)
61+
}
62+
}
63+
64+
impl std::error::Error for Error {}
65+
66+
impl Diagnostic for Error {}
67+
68+
struct Processor {
69+
rx: mpsc::Receiver<u8>,
70+
stdin: ChildStdin,
71+
child: Child,
72+
}
73+
74+
impl Processor {
75+
pub fn new(child: Child) -> Self {
76+
let mut child = child;
77+
let (tx, rx) = mpsc::channel::<u8>();
78+
79+
let mut stdout = child.stdout.take().unwrap();
80+
let stdin = child.stdin.take().unwrap();
81+
82+
std::thread::spawn(move || {
83+
let mut buffer = [0u8; 1024];
84+
loop {
85+
if let Ok(len) = stdout.read(&mut buffer) {
86+
for b in &buffer[..len] {
87+
if tx.send(*b).is_err() {
88+
break;
89+
}
90+
}
91+
}
92+
}
93+
});
94+
95+
Self { rx, stdin, child }
96+
}
97+
98+
pub fn try_receive(&mut self) -> Vec<u8> {
99+
let mut res = Vec::new();
100+
while let Ok(b) = self.rx.try_recv() {
101+
res.push(b);
102+
}
103+
res
104+
}
105+
106+
pub fn send(&mut self, data: Vec<u8>) {
107+
self.stdin.write(&data).ok();
108+
}
109+
}
110+
111+
impl Drop for Processor {
112+
fn drop(&mut self) {
113+
self.child.kill().unwrap();
114+
}
115+
}
116+
117+
pub struct ExternalProcessors {
118+
processors: Vec<Processor>,
119+
}
120+
121+
impl ExternalProcessors {
122+
pub fn new(processors: Option<String>, elf: Option<PathBuf>) -> Result<Self, Error> {
123+
let mut args = Vec::new();
124+
125+
if let Some(elf) = elf {
126+
args.push(elf.as_os_str().to_str().unwrap().to_string());
127+
};
128+
129+
let mut spawned = Vec::new();
130+
if let Some(processors) = processors {
131+
for processor in processors.split(",").into_iter() {
132+
let processor = std::process::Command::new(processor)
133+
.args(args.clone())
134+
.stdin(Stdio::piped())
135+
.stdout(Stdio::piped())
136+
.stderr(Stdio::inherit())
137+
.spawn()
138+
.map_err(|_| Error {
139+
executable: processor.to_string(),
140+
})?;
141+
spawned.push(Processor::new(processor));
142+
}
143+
}
144+
145+
Ok(Self {
146+
processors: spawned,
147+
})
148+
}
149+
150+
pub fn process(&mut self, read: &[u8]) -> Vec<u8> {
151+
let mut buffer = Vec::new();
152+
buffer.extend_from_slice(read);
153+
154+
for processor in &mut self.processors {
155+
processor.send(buffer);
156+
buffer = processor.try_receive();
157+
}
158+
159+
buffer
160+
}
161+
}

espflash/src/cli/monitor/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
use std::{
1414
io::{stdout, ErrorKind, Read, Write},
15+
path::PathBuf,
1516
time::Duration,
1617
};
1718

@@ -20,6 +21,7 @@ use crossterm::{
2021
event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers},
2122
terminal::{disable_raw_mode, enable_raw_mode},
2223
};
24+
use external_processors::ExternalProcessors;
2325
use log::error;
2426
use miette::{IntoDiagnostic, Result};
2527
#[cfg(feature = "serialport")]
@@ -31,6 +33,7 @@ use crate::{
3133
connection::{reset::reset_after_flash, Port},
3234
};
3335

36+
pub mod external_processors;
3437
pub mod parser;
3538

3639
mod line_endings;
@@ -73,6 +76,8 @@ pub fn monitor(
7376
baud: u32,
7477
log_format: LogFormat,
7578
interactive_mode: bool,
79+
processors: Option<String>,
80+
elf_file: Option<PathBuf>,
7681
) -> miette::Result<()> {
7782
if interactive_mode {
7883
println!("Commands:");
@@ -101,6 +106,8 @@ pub fn monitor(
101106
LogFormat::Serial => Box::new(parser::serial::Serial),
102107
};
103108

109+
let mut external_processors = ExternalProcessors::new(processors, elf_file)?;
110+
104111
let mut buff = [0; 1024];
105112
loop {
106113
let read_count = match serial.read(&mut buff) {
@@ -110,7 +117,8 @@ pub fn monitor(
110117
err => err.into_diagnostic(),
111118
}?;
112119

113-
parser.feed(&buff[0..read_count], &mut stdout);
120+
let processed = external_processors.process(&buff[0..read_count]);
121+
parser.feed(&processed, &mut stdout);
114122

115123
// Don't forget to flush the writer!
116124
stdout.flush().ok();

0 commit comments

Comments
 (0)