extern crate std;
use crate::{
prelude::{vec, Bytes, String},
StdioConfig, StdioError, StdioSystem, System,
};
use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort};
use protoflow_derive::Block;
use simple_mermaid::mermaid;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WriteFlags {
pub create: bool,
pub append: bool,
}
impl Default for WriteFlags {
fn default() -> Self {
Self {
create: true,
append: true,
}
}
}
#[doc = mermaid!("../../../doc/sys/write_file.mmd")]
#[doc = mermaid!("../../../doc/sys/write_file.seq.mmd" framed)]
#[derive(Block, Clone)]
pub struct WriteFile {
#[input]
pub path: InputPort<String>,
#[input]
pub input: InputPort<Bytes>,
#[parameter]
pub flags: WriteFlags,
}
impl WriteFile {
pub fn new(path: InputPort<String>, input: InputPort<Bytes>) -> Self {
Self::with_params(path, input, None)
}
pub fn with_params(
path: InputPort<String>,
input: InputPort<Bytes>,
flags: Option<WriteFlags>,
) -> Self {
Self {
path,
input,
flags: flags.unwrap_or_default(),
}
}
pub fn with_system(system: &System, flags: Option<WriteFlags>) -> Self {
use crate::SystemBuilding;
Self::with_params(system.input(), system.input(), flags)
}
pub fn with_flags(self, flags: WriteFlags) -> Self {
WriteFile { flags, ..self }
}
}
impl Block for WriteFile {
fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult {
use std::io::prelude::Write;
runtime.wait_for(&self.path)?;
let Some(path) = self.path.recv()? else {
return Ok(());
};
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(self.flags.create)
.append(self.flags.append)
.truncate(!self.flags.append)
.open(path)?;
while let Some(message) = self.input.recv()? {
file.write_all(&message)?;
}
drop(file);
self.input.close()?;
Ok(())
}
}
#[cfg(feature = "std")]
impl StdioSystem for WriteFile {
fn build_system(config: StdioConfig) -> Result<System, StdioError> {
use crate::{CoreBlocks, SysBlocks, SystemBuilding};
config.allow_only(vec!["path", "create", "append"])?;
let path = config.get_string("path")?;
let create = config.get_opt::<bool>("create")?;
let append = config.get_opt::<bool>("append")?;
let default_flags = WriteFlags::default();
let flags = WriteFlags {
create: create.unwrap_or(default_flags.create),
append: append.unwrap_or(default_flags.append),
};
Ok(System::build(|s| {
let stdin = config.read_stdin(s);
let path_const = s.const_string(path);
let write_file = s.write_file().with_flags(flags);
s.connect(&path_const.output, &write_file.path);
s.connect(&stdin.output, &write_file.input);
}))
}
}
#[cfg(test)]
mod tests {
extern crate std;
use crate::{CoreBlocks, IoBlocks, SysBlocks, System, SystemBuilding, WriteFile};
#[test]
fn instantiate_block() {
let _ = System::build(|s| {
let _ = s.block(WriteFile::with_system(s, None));
});
}
#[test]
fn run_block() {
use std::{fs::File, io::Read, string::String};
let temp_dir = std::env::temp_dir();
let output_path = temp_dir.join("write-file-test.txt");
let _ = std::fs::remove_file(&output_path);
System::run(|s| {
let path = s.const_string(output_path.display());
let content = s.const_string("Hello world!");
let line_encoder = s.encode_lines();
let write_file = s.write_file();
s.connect(&content.output, &line_encoder.input);
s.connect(&path.output, &write_file.path);
s.connect(&line_encoder.output, &write_file.input);
})
.expect("system execution failed");
let mut file = File::open(&output_path).expect("failed to open file for system output");
let mut file_content = String::new();
file.read_to_string(&mut file_content)
.expect("failed to read system output");
assert_eq!("Hello world!\n", file_content);
drop(file);
std::fs::remove_file(&output_path).expect("failed to remove temp file");
}
}