protoflow_blocks/blocks/sys/
write_file.rs1extern crate std;
4
5use crate::{
6 prelude::{vec, Bytes, String},
7 StdioConfig, StdioError, StdioSystem, System,
8};
9use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort};
10use protoflow_derive::Block;
11use simple_mermaid::mermaid;
12
13#[derive(Clone, Copy, Debug)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct WriteFlags {
16 pub create: bool,
17 pub append: bool,
18}
19
20impl Default for WriteFlags {
21 fn default() -> Self {
22 Self {
23 create: true,
24 append: true,
25 }
26 }
27}
28
29#[doc = mermaid!("../../../doc/sys/write_file.mmd")]
33#[doc = mermaid!("../../../doc/sys/write_file.seq.mmd" framed)]
36#[derive(Block, Clone)]
57pub struct WriteFile {
58 #[input]
60 pub path: InputPort<String>,
61
62 #[input]
64 pub input: InputPort<Bytes>,
65
66 #[parameter]
67 pub flags: WriteFlags,
68}
69
70impl WriteFile {
71 pub fn new(path: InputPort<String>, input: InputPort<Bytes>) -> Self {
72 Self::with_params(path, input, None)
73 }
74
75 pub fn with_params(
76 path: InputPort<String>,
77 input: InputPort<Bytes>,
78 flags: Option<WriteFlags>,
79 ) -> Self {
80 Self {
81 path,
82 input,
83 flags: flags.unwrap_or_default(),
84 }
85 }
86
87 pub fn with_system(system: &System, flags: Option<WriteFlags>) -> Self {
88 use crate::SystemBuilding;
89 Self::with_params(system.input(), system.input(), flags)
90 }
91
92 pub fn with_flags(self, flags: WriteFlags) -> Self {
93 WriteFile { flags, ..self }
94 }
95}
96
97impl Block for WriteFile {
98 fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult {
99 use std::io::prelude::Write;
100
101 runtime.wait_for(&self.path)?;
102
103 let Some(path) = self.path.recv()? else {
104 return Ok(());
105 };
106 let mut file = std::fs::OpenOptions::new()
107 .write(true)
108 .create(self.flags.create)
109 .append(self.flags.append)
110 .truncate(!self.flags.append)
111 .open(path)?;
112
113 while let Some(message) = self.input.recv()? {
114 file.write_all(&message)?;
115 }
116
117 drop(file);
118
119 self.input.close()?;
120 Ok(())
121 }
122}
123
124#[cfg(feature = "std")]
125impl StdioSystem for WriteFile {
126 fn build_system(config: StdioConfig) -> Result<System, StdioError> {
127 use crate::{CoreBlocks, SysBlocks, SystemBuilding};
128
129 config.allow_only(vec!["path", "create", "append"])?;
130 let path = config.get_string("path")?;
131
132 let create = config.get_opt::<bool>("create")?;
133 let append = config.get_opt::<bool>("append")?;
134
135 let default_flags = WriteFlags::default();
136
137 let flags = WriteFlags {
138 create: create.unwrap_or(default_flags.create),
139 append: append.unwrap_or(default_flags.append),
140 };
141
142 Ok(System::build(|s| {
143 let stdin = config.read_stdin(s);
144 let path_const = s.const_string(path);
145 let write_file = s.write_file().with_flags(flags);
146
147 s.connect(&path_const.output, &write_file.path);
148 s.connect(&stdin.output, &write_file.input);
149 }))
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 extern crate std;
156 use crate::{CoreBlocks, IoBlocks, SysBlocks, System, SystemBuilding, WriteFile};
157
158 #[test]
159 fn instantiate_block() {
160 let _ = System::build(|s| {
162 let _ = s.block(WriteFile::with_system(s, None));
163 });
164 }
165
166 #[test]
167 fn run_block() {
168 use std::{fs::File, io::Read, string::String};
169
170 let temp_dir = std::env::temp_dir();
171 let output_path = temp_dir.join("write-file-test.txt");
172
173 let _ = std::fs::remove_file(&output_path);
175
176 System::run(|s| {
177 let path = s.const_string(output_path.display());
178 let content = s.const_string("Hello world!");
179 let line_encoder = s.encode_lines();
180 let write_file = s.write_file();
181 s.connect(&content.output, &line_encoder.input);
182 s.connect(&path.output, &write_file.path);
183 s.connect(&line_encoder.output, &write_file.input);
184 })
185 .expect("system execution failed");
186
187 let mut file = File::open(&output_path).expect("failed to open file for system output");
188
189 let mut file_content = String::new();
190 file.read_to_string(&mut file_content)
191 .expect("failed to read system output");
192
193 assert_eq!("Hello world!\n", file_content);
194
195 drop(file);
196
197 std::fs::remove_file(&output_path).expect("failed to remove temp file");
198 }
199}