├── .gitattributes ├── VERSION ├── lib ├── protoflow │ ├── tests │ │ ├── .gitkeep │ │ ├── mpsc.rs │ │ └── zst.rs │ ├── benches │ │ └── .gitkeep │ ├── examples │ │ ├── .gitkeep │ │ ├── echo_lines │ │ │ ├── main.rs │ │ │ └── README.md │ │ ├── count_lines │ │ │ ├── main.rs │ │ │ └── README.md │ │ └── async │ │ │ └── main.rs │ ├── src │ │ ├── commands │ │ │ ├── config.rs │ │ │ ├── check.rs │ │ │ ├── generate.rs │ │ │ └── execute.rs │ │ ├── feature.rs │ │ ├── lib.rs │ │ ├── exit.rs │ │ └── main.rs │ ├── build.rs │ └── Cargo.toml ├── protoflow-core │ ├── tests │ │ └── .gitkeep │ ├── src │ │ ├── runtimes │ │ │ └── web.rs │ │ ├── function_block.rs │ │ ├── transports.rs │ │ ├── runtimes.rs │ │ ├── transports │ │ │ └── mpsc │ │ │ │ ├── event.rs │ │ │ │ ├── output.rs │ │ │ │ └── input.rs │ │ ├── process.rs │ │ ├── runtime.rs │ │ ├── block_runtime.rs │ │ ├── message_buffer.rs │ │ ├── message_receiver.rs │ │ ├── async_block.rs │ │ ├── port_state.rs │ │ ├── message.rs │ │ ├── parameter_descriptor.rs │ │ ├── message_sender.rs │ │ ├── prelude.rs │ │ ├── block_error.rs │ │ ├── port_error.rs │ │ ├── utils │ │ │ └── rw_condvar.rs │ │ ├── transport.rs │ │ ├── port.rs │ │ ├── block_descriptor.rs │ │ ├── block.rs │ │ ├── lib.rs │ │ ├── output_ports.rs │ │ ├── input_ports.rs │ │ ├── port_descriptor.rs │ │ ├── port_id.rs │ │ └── output_port.rs │ └── Cargo.toml ├── protoflow-blocks │ ├── tests │ │ ├── .gitkeep │ │ └── json_roundtrip.rs │ ├── src │ │ ├── blocks │ │ │ ├── flow │ │ │ │ └── .gitkeep │ │ │ ├── math │ │ │ │ └── .gitkeep │ │ │ ├── text │ │ │ │ ├── .gitkeep │ │ │ │ ├── decode_csv.rs │ │ │ │ └── split_string.rs │ │ │ ├── flow.rs │ │ │ ├── math.rs │ │ │ ├── core │ │ │ │ ├── drop.rs │ │ │ │ ├── buffer.rs │ │ │ │ ├── random.rs │ │ │ │ └── const.rs │ │ │ ├── sys │ │ │ │ ├── write_stdout.rs │ │ │ │ ├── write_stderr.rs │ │ │ │ ├── read_env.rs │ │ │ │ ├── read_dir.rs │ │ │ │ ├── read_stdin.rs │ │ │ │ └── read_file.rs │ │ │ ├── hash.rs │ │ │ ├── io │ │ │ │ ├── encode_hex.rs │ │ │ │ └── encode.rs │ │ │ └── text.rs │ │ ├── block_instantiation.rs │ │ ├── types.rs │ │ ├── block_connections.rs │ │ ├── types │ │ │ ├── encoding.rs │ │ │ ├── delay_type.rs │ │ │ └── byte_size.rs │ │ ├── lib.rs │ │ └── stdio.rs │ ├── doc │ │ ├── core │ │ │ ├── const.mmd │ │ │ ├── drop.mmd │ │ │ ├── random.mmd │ │ │ ├── buffer.mmd │ │ │ ├── delay.mmd │ │ │ ├── const.seq.mmd │ │ │ ├── random.seq.mmd │ │ │ ├── drop.seq.mmd │ │ │ ├── buffer.seq.mmd │ │ │ ├── count.mmd │ │ │ ├── delay.seq.mmd │ │ │ └── count.seq.mmd │ │ ├── sys │ │ │ ├── read_socket.mmd │ │ │ ├── read_stdin.mmd │ │ │ ├── write_socket.mmd │ │ │ ├── write_stderr.mmd │ │ │ ├── write_stdout.mmd │ │ │ ├── read_dir.mmd │ │ │ ├── read_env.mmd │ │ │ ├── read_file.mmd │ │ │ ├── write_file.mmd │ │ │ ├── read_stdin.seq.mmd │ │ │ ├── read_socket.seq.mmd │ │ │ ├── write_socket.seq.mmd │ │ │ ├── write_stderr.seq.mmd │ │ │ ├── write_stdout.seq.mmd │ │ │ ├── read_env.seq.mmd │ │ │ ├── read_dir.seq.mmd │ │ │ ├── read_file.seq.mmd │ │ │ └── write_file.seq.mmd │ │ ├── io │ │ │ ├── decode.mmd │ │ │ ├── encode.mmd │ │ │ ├── decode_hex.mmd │ │ │ ├── encode_hex.mmd │ │ │ ├── decode_json.mmd │ │ │ ├── encode_json.mmd │ │ │ ├── decode.seq.mmd │ │ │ ├── encode.seq.mmd │ │ │ ├── decode_hex.seq.mmd │ │ │ ├── encode_hex.seq.mmd │ │ │ ├── decode_json.seq.mmd │ │ │ └── encode_json.seq.mmd │ │ ├── text │ │ │ ├── split_string.mmd │ │ │ ├── concat_strings.mmd │ │ │ ├── decode_csv.mmd │ │ │ ├── encode_csv.mmd │ │ │ ├── decode_csv.seq.mmd │ │ │ ├── encode_csv.seq.mmd │ │ │ ├── split_string.seq.mmd │ │ │ └── concat_strings.seq.mmd │ │ └── hash │ │ │ ├── hash.mmd │ │ │ └── hash.seq.mmd │ └── Cargo.toml ├── protoflow-crossbeam │ ├── tests │ │ └── .gitkeep │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── protoflow-derive │ ├── tests │ │ ├── .gitkeep │ │ └── function_block.rs │ ├── src │ │ ├── derives.rs │ │ ├── util.rs │ │ ├── meta.rs │ │ ├── derives │ │ │ └── system.rs │ │ └── lib.rs │ └── Cargo.toml ├── protoflow-flume │ ├── tests │ │ └── .gitkeep │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── protoflow-syntax │ ├── tests │ │ └── .gitkeep │ ├── src │ │ ├── lib.rs │ │ ├── analysis_error.rs │ │ └── codegen.rs │ └── Cargo.toml └── protoflow-zeromq │ ├── tests │ └── .gitkeep │ ├── src │ └── lib.rs │ └── Cargo.toml ├── .github ├── CODEOWNERS └── workflows │ └── release.yaml ├── AUTHORS ├── rustfmt.toml ├── .cargo └── config.toml ├── Makefile ├── .gitignore ├── examples └── hello.rs ├── UNLICENSE ├── Cargo.toml └── CHANGES.md /.gitattributes: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.4.3 2 | -------------------------------------------------------------------------------- /lib/protoflow/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @artob 2 | -------------------------------------------------------------------------------- /lib/protoflow-core/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow/benches/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow/examples/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-crossbeam/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-derive/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-flume/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-syntax/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-zeromq/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/flow/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/math/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/text/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Arto Bendiken 2 | * Joshua J. Bouw 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # See: https://rust-lang.github.io/rustfmt/ 2 | 3 | reorder_imports = true 4 | imports_granularity = "Crate" 5 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/runtimes/web.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | pub struct Web {} 4 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # See: https://doc.rust-lang.org/cargo/reference/config.html 2 | # See: https://docs.shipyard.rs/configuration/git-fetch-with-cli.html 3 | 4 | [net] 5 | git-fetch-with-cli = true 6 | -------------------------------------------------------------------------------- /lib/protoflow-flume/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![no_std] 4 | #![deny(unsafe_code)] 5 | 6 | #[doc(hidden)] 7 | pub use protoflow_core::prelude; 8 | -------------------------------------------------------------------------------- /lib/protoflow-zeromq/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![no_std] 4 | #![deny(unsafe_code)] 5 | 6 | #[doc(hidden)] 7 | pub use protoflow_core::prelude; 8 | -------------------------------------------------------------------------------- /lib/protoflow-crossbeam/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![no_std] 4 | #![deny(unsafe_code)] 5 | 6 | #[doc(hidden)] 7 | pub use protoflow_core::prelude; 8 | -------------------------------------------------------------------------------- /lib/protoflow/src/commands/config.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::exit::ExitCode; 4 | 5 | #[allow(dead_code)] 6 | pub fn config() -> Result<(), ExitCode> { 7 | Ok(()) // TODO 8 | } 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CARGO = cargo 2 | 3 | all: Cargo.toml 4 | $(CARGO) build 5 | 6 | check: Cargo.toml 7 | $(CARGO) test -- --nocapture 8 | 9 | clean: Cargo.toml 10 | @rm -rf *~ target 11 | $(CARGO) clean 12 | 13 | .PHONY: all check clean 14 | .SECONDARY: 15 | .SUFFIXES: 16 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/function_block.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{prelude::Result, BlockError}; 4 | 5 | pub trait FunctionBlock { 6 | fn compute(&self, input: I) -> Result; 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Environment variables 5 | .env 6 | 7 | # Jetbrains 8 | .idea/ 9 | 10 | # Visual Studio Code 11 | .vscode/ 12 | 13 | # Editor backup files 14 | *~ 15 | 16 | # Rust artifacts 17 | /Cargo.lock 18 | /rust-toolchain 19 | /target 20 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/const.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Const space:2 Sink 4 | Const-- "output" -->Sink 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class Const block 9 | class Sink hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/drop.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Source space:2 Drop 4 | Source-- "input" -->Drop 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class Drop block 9 | class Source hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/random.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Random space:2 Sink 4 | Random-- "output" -->Sink 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class Random block 9 | class Sink hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-derive/src/derives.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | mod block; 4 | pub(crate) use block::*; 5 | 6 | mod function_block; 7 | pub(crate) use function_block::*; 8 | 9 | mod system; 10 | pub(crate) use system::*; 11 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/buffer.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Source space:2 Buffer 4 | Source-- "input" -->Buffer 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class Buffer block 9 | class Source hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/transports.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #[cfg(feature = "std")] 4 | pub type MockTransport = MpscTransport; 5 | 6 | #[cfg(feature = "std")] 7 | mod mpsc; 8 | #[cfg(feature = "std")] 9 | pub use mpsc::*; 10 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_socket.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | ReadSocket space:2 Sink 4 | ReadSocket-- "output" -->Sink 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class ReadSocket block 9 | class Sink hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_stdin.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | ReadStdin space:2 Sink 4 | ReadStdin-- "output" -->Sink 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class ReadStdin block 9 | class Sink hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_socket.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Source space:2 WriteSocket 4 | Source-- "input" -->WriteSocket 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class WriteSocket block 9 | class Source hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_stderr.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Source space:2 WriteStderr 4 | Source-- "input" -->WriteStderr 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class WriteStderr block 9 | class Source hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_stdout.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Source space:2 WriteStdout 4 | Source-- "input" -->WriteStdout 5 | 6 | classDef block height:48px,padding:8px; 7 | classDef hidden visibility:none; 8 | class WriteStdout block 9 | class Source hidden 10 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/runtimes.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #[cfg(feature = "std")] 4 | mod std; 5 | #[cfg(feature = "std")] 6 | pub use std::*; 7 | 8 | //#[cfg(feature = "web")] 9 | //mod web; 10 | //#[cfg(feature = "web")] 11 | //pub use web::*; 12 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/transports/mpsc/event.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::Bytes; 4 | 5 | #[derive(Clone, Debug)] 6 | pub enum MpscTransportEvent { 7 | #[allow(unused)] 8 | Connect, 9 | Message(Bytes), 10 | Disconnect, 11 | } 12 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/process.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::BlockResult; 4 | 5 | pub type ProcessID = usize; 6 | 7 | pub trait Process { 8 | fn id(&self) -> ProcessID; 9 | fn is_alive(&self) -> bool; 10 | fn join(&self) -> BlockResult; 11 | } 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/delay.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 Delay space:2 Sink 4 | Source-- "input" -->Delay 5 | Delay-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class Delay block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/decode.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 Decode space:2 Sink 4 | Source-- "input" -->Decode 5 | Decode-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class Decode block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/encode.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 Encode space:2 Sink 4 | Source-- "input" -->Encode 5 | Encode-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class Encode block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/const.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant Const as Const block 4 | participant Const.output as Const.output port 5 | participant BlockA as Another block 6 | 7 | Const-->>BlockA: Connect 8 | 9 | Const->>BlockA: Message 10 | 11 | Const-->>Const.output: Close 12 | Const-->>BlockA: Disconnect 13 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/decode_hex.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 DecodeHex space:2 Sink 4 | Source-- "input" -->DecodeHex 5 | DecodeHex-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class DecodeHex block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/encode_hex.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 EncodeHex space:2 Sink 4 | Source-- "input" -->EncodeHex 5 | EncodeHex-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class EncodeHex block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/decode_json.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 DecodeJSON space:2 Sink 4 | Source-- "input" -->DecodeJSON 5 | DecodeJSON-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class DecodeJSON block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/encode_json.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 EncodeJSON space:2 Sink 4 | Source-- "input" -->EncodeJSON 5 | EncodeJSON-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class EncodeJSON block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/random.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant Random as Random block 4 | participant Random.output as Random.output port 5 | participant BlockA as Another block 6 | 7 | Random-->>BlockA: Connect 8 | 9 | Random->>BlockA: Message 10 | 11 | Random-->>Random.output: Close 12 | Random-->>BlockA: Disconnect 13 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/split_string.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 SplitString space:2 Sink 4 | Source-- "input" -->SplitString 5 | SplitString-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class SplitString block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![no_std] 4 | #![deny(unsafe_code)] 5 | 6 | #[doc(hidden)] 7 | pub use protoflow_core::prelude; 8 | 9 | mod analysis_error; 10 | pub use analysis_error::*; 11 | 12 | mod codegen; 13 | pub use codegen::*; 14 | 15 | mod system_parser; 16 | pub use system_parser::*; 17 | -------------------------------------------------------------------------------- /lib/protoflow/examples/echo_lines/main.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use protoflow::{blocks::*, BlockResult}; 4 | 5 | pub fn main() -> BlockResult { 6 | System::run(|s| { 7 | let stdin = s.read_stdin(); 8 | let stdout = s.write_stdout(); 9 | s.connect(&stdin.output, &stdout.input); 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/concat_strings.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 ConcatStrings space:2 Sink 4 | Source-- "input" -->ConcatStrings 5 | ConcatStrings-- "output" -->Sink 6 | 7 | classDef block height:48px,padding:8px; 8 | classDef hidden visibility:none; 9 | class ConcatStrings block 10 | class Source hidden 11 | class Sink hidden 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_dir.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Config space:3 4 | space:4 5 | space:4 6 | ReadDir space:2 Sink 7 | Config-- "path" -->ReadDir 8 | ReadDir-- "output" -->Sink 9 | 10 | classDef block height:48px,padding:8px; 11 | classDef hidden visibility:none; 12 | class ReadDir block 13 | class Config hidden 14 | class Sink hidden 15 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_env.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Config space:3 4 | space:4 5 | space:4 6 | ReadEnv space:2 Sink 7 | Config-- "name" -->ReadEnv 8 | ReadEnv-- "output" -->Sink 9 | 10 | classDef block height:48px,padding:8px; 11 | classDef hidden visibility:none; 12 | class ReadEnv block 13 | class Config hidden 14 | class Sink hidden 15 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_file.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | Config space:3 4 | space:4 5 | space:4 6 | ReadFile space:2 Sink 7 | Config-- "path" -->ReadFile 8 | ReadFile-- "output" -->Sink 9 | 10 | classDef block height:48px,padding:8px; 11 | classDef hidden visibility:none; 12 | class ReadFile block 13 | class Config hidden 14 | class Sink hidden 15 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/block_instantiation.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use super::{prelude::Box, System}; 4 | use protoflow_core::Block; 5 | 6 | /// A trait for instantiating a block in a given system. 7 | pub trait BlockInstantiation { 8 | fn instantiate(&self, _system: &mut System) -> Box { 9 | unimplemented!() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_file.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 4 3 | space:3 Config 4 | space:4 5 | space:4 6 | Source space:2 WriteFile 7 | Config-- "path" -->WriteFile 8 | Source-- "input" -->WriteFile 9 | 10 | classDef block height:48px,padding:8px; 11 | classDef hidden visibility:none; 12 | class WriteFile block 13 | class Config hidden 14 | class Source hidden 15 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/drop.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant Drop.input as Drop.input port 5 | participant Drop as Drop block 6 | 7 | BlockA-->>Drop: Connect 8 | 9 | loop Drop process 10 | BlockA->>Drop: Message 11 | Drop->>Drop: Drop message 12 | end 13 | 14 | BlockA-->>Drop: Disconnect 15 | Drop-->>Drop.input: Close 16 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/buffer.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant Buffer.input as Buffer.input port 5 | participant Buffer as Buffer block 6 | 7 | BlockA-->>Buffer: Connect 8 | 9 | loop Buffer process 10 | BlockA->>Buffer: Message 11 | Buffer->>Buffer: Store message 12 | end 13 | 14 | BlockA-->>Buffer: Disconnect 15 | Buffer-->>Buffer.input: Close 16 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/hash/hash.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 Hash space:2 Sink 4 | space:7 5 | space:7 6 | space:3 Result space:3 7 | Source-- "input" -->Hash 8 | Hash-- "output" -->Sink 9 | Hash-- "hash" -->Result 10 | 11 | classDef block height:48px,padding:8px; 12 | classDef hidden visibility:none; 13 | class Hash block 14 | class Source hidden 15 | class Sink hidden 16 | class Result hidden 17 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/count.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | Source space:2 Count space:2 Sink 4 | space:7 5 | space:7 6 | space:3 Result space:3 7 | Source-- "input" -->Count 8 | Count-- "output" -->Sink 9 | Count-- "count" -->Result 10 | 11 | classDef block height:48px,padding:8px; 12 | classDef hidden visibility:none; 13 | class Count block 14 | class Source hidden 15 | class Sink hidden 16 | class Result hidden 17 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/decode_csv.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | space:5 Sink1 space:1 4 | space:1 Source space:1 DecodeCSV space:3 5 | space:5 Sink2 space:1 6 | Source-- "input" -->DecodeCSV 7 | DecodeCSV-- "header" -->Sink1 8 | DecodeCSV-- "rows" -->Sink2 9 | 10 | classDef block height:48px,padding:8px; 11 | classDef hidden visibility:none; 12 | class DecodeCSV block 13 | class Source hidden 14 | class Sink1 hidden 15 | class Sink2 hidden 16 | -------------------------------------------------------------------------------- /lib/protoflow/src/commands/check.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::exit::ExitCode; 4 | use protoflow_syntax::SystemParser; 5 | use std::path::PathBuf; 6 | 7 | pub fn check(paths: Vec) -> Result<(), ExitCode> { 8 | for path in paths { 9 | let mut parser = SystemParser::from_file(path)?; 10 | let _ = parser.check()?; 11 | } 12 | Ok(()) 13 | } 14 | 15 | #[derive(Clone, Debug)] 16 | pub enum CheckError {} 17 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/encode_csv.mmd: -------------------------------------------------------------------------------- 1 | block-beta 2 | columns 7 3 | space:1 Source1 space:5 4 | space:3 EncodeCSV space:1 Sink space:1 5 | space:1 Source2 space:5 6 | Source1-- "header" -->EncodeCSV 7 | Source2-- "rows" -->EncodeCSV 8 | EncodeCSV-- "output" -->Sink 9 | 10 | classDef block height:48px,padding:8px; 11 | classDef hidden visibility:none; 12 | class EncodeCSV block 13 | class Source1 hidden 14 | class Source2 hidden 15 | class Sink hidden 16 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_stdin.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant ReadStdin as ReadStdin block 4 | participant ReadStdin.output as ReadStdin.output port 5 | participant BlockA as Another block 6 | 7 | ReadStdin-->>BlockA: Connect 8 | 9 | loop ReadStdin process 10 | ReadStdin->>ReadStdin: Read bytes from standard input 11 | ReadStdin->>BlockA: Message (Bytes) 12 | end 13 | 14 | ReadStdin-->>ReadStdin.output: Close 15 | ReadStdin-->>BlockA: Disconnect 16 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_socket.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant ReadSocket as ReadSocket block 4 | participant ReadSocket.output as ReadSocket.output port 5 | participant BlockA as Another block 6 | 7 | ReadSocket-->>BlockA: Connect 8 | 9 | loop ReadSocket process 10 | ReadSocket->>ReadSocket: Read bytes from a TCP socket 11 | ReadSocket->>BlockA: Message (Bytes) 12 | end 13 | 14 | ReadSocket-->>ReadSocket.output: Close 15 | ReadSocket-->>BlockA: Disconnect 16 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_socket.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant WriteSocket.input as WriteSocket.input port 5 | participant WriteSocket as WriteSocket block 6 | 7 | BlockA-->>WriteSocket: Connect 8 | 9 | loop WriteSocket process 10 | BlockA->>WriteSocket: Message (Bytes) 11 | WriteSocket->>WriteSocket: Write bytes to a TCP socket 12 | end 13 | 14 | BlockA-->>WriteSocket: Disconnect 15 | WriteSocket-->>WriteSocket.input: Close 16 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_stderr.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant WriteStderr.input as WriteStderr.input port 5 | participant WriteStderr as WriteStderr block 6 | 7 | BlockA-->>WriteStderr: Connect 8 | 9 | loop WriteStderr process 10 | BlockA->>WriteStderr: Message (Bytes) 11 | WriteStderr->>WriteStderr: Write bytes to standard error 12 | end 13 | 14 | BlockA-->>WriteStderr: Disconnect 15 | WriteStderr-->>WriteStderr.input: Close 16 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_stdout.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant WriteStdout.input as WriteStdout.input port 5 | participant WriteStdout as WriteStdout block 6 | 7 | BlockA-->>WriteStdout: Connect 8 | 9 | loop WriteStdout process 10 | BlockA->>WriteStdout: Message (Bytes) 11 | WriteStdout->>WriteStdout: Write bytes to standard output 12 | end 13 | 14 | BlockA-->>WriteStdout: Disconnect 15 | WriteStdout-->>WriteStdout.input: Close 16 | -------------------------------------------------------------------------------- /lib/protoflow/src/commands/generate.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::exit::ExitCode; 4 | use protoflow_syntax::{Code, SystemParser}; 5 | use std::path::PathBuf; 6 | 7 | pub fn generate(path: PathBuf) -> Result<(), ExitCode> { 8 | let mut parser = SystemParser::from_file(path)?; 9 | let model = parser.check()?; 10 | let code = Code::try_from(model)?; 11 | std::print!("{}", code.unparse()); 12 | Ok(()) 13 | } 14 | 15 | #[derive(Clone, Debug)] 16 | pub enum GenerateError {} 17 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/types.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | mod byte_size; 4 | pub use byte_size::*; 5 | 6 | mod delay_type; 7 | pub use delay_type::*; 8 | 9 | mod encoding; 10 | pub use encoding::*; 11 | 12 | #[cfg(any( 13 | feature = "hash-blake3", 14 | feature = "hash-md5", 15 | feature = "hash-sha1", 16 | feature = "hash-sha2" 17 | ))] 18 | mod hash_algorithm; 19 | #[cfg(any( 20 | feature = "hash-blake3", 21 | feature = "hash-md5", 22 | feature = "hash-sha1", 23 | feature = "hash-sha2" 24 | ))] 25 | pub use hash_algorithm::*; 26 | -------------------------------------------------------------------------------- /examples/hello.rs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rust-script 2 | //! This is free and unencumbered software released into the public domain. 3 | //! 4 | //! ```cargo 5 | //! [dependencies] 6 | //! protoflow = "0.2" 7 | //! ``` 8 | 9 | use protoflow::{blocks::*, BlockResult}; 10 | 11 | fn main() -> BlockResult { 12 | System::run(|s| { 13 | let greeting = s.const_string("Hello, world!"); 14 | 15 | let line_encoder = s.encode_lines(); 16 | s.connect(&greeting.output, &line_encoder.input); 17 | 18 | let stdout = s.write_stdout(); 19 | s.connect(&line_encoder.output, &stdout.input); 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /lib/protoflow-derive/tests/function_block.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #[test] 4 | fn define_function_block() { 5 | use protoflow_core::{BlockResult, FunctionBlock, InputPort, OutputPort}; 6 | use protoflow_derive::FunctionBlock; 7 | 8 | /// A block that simply echoes inputs to outputs. 9 | #[derive(FunctionBlock, Clone)] 10 | pub struct Echo(pub InputPort, pub OutputPort); 11 | 12 | impl FunctionBlock for Echo { 13 | fn compute(&self, input: i64) -> BlockResult { 14 | Ok(input) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/protoflow/build.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use std::collections::BTreeSet; 4 | 5 | fn main() -> std::io::Result<()> { 6 | // See: https://github.com/baoyachi/shadow-rs 7 | // Omit all nonpublic and/or sensitive information: 8 | let mut omit = BTreeSet::new(); 9 | omit.insert(shadow_rs::CARGO_TREE); 10 | omit.insert(shadow_rs::CARGO_MANIFEST_DIR); 11 | omit.insert(shadow_rs::COMMIT_AUTHOR); 12 | omit.insert(shadow_rs::COMMIT_EMAIL); 13 | omit.insert(shadow_rs::GIT_STATUS_FILE); 14 | shadow_rs::new_deny(omit).unwrap(); 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /lib/protoflow-derive/src/util.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use proc_macro_crate::{crate_name, FoundCrate}; 5 | use quote::quote; 6 | use syn::Ident; 7 | 8 | pub(crate) fn protoflow_crate() -> TokenStream { 9 | let found_crate = 10 | crate_name("protoflow-core").expect("protoflow-core is present in `Cargo.toml`"); 11 | match found_crate { 12 | FoundCrate::Itself => quote!(crate), 13 | FoundCrate::Name(name) => { 14 | let ident = Ident::new(&name, Span::call_site()); 15 | quote!(#ident) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/runtime.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{Box, Rc}, 5 | Block, BlockResult, BoxedBlockType, Process, System, Transport, 6 | }; 7 | 8 | pub trait Runtime { 9 | //fn execute(&mut self, block: T) -> BlockResult> { 10 | // self.execute_block(Box::new(block)) 11 | //} 12 | 13 | fn execute_block(&mut self, block: BoxedBlockType) -> BlockResult>; 14 | 15 | fn execute( 16 | &mut self, 17 | system: System, 18 | ) -> BlockResult>; 19 | } 20 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/delay.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant Delay.input as Delay.input port 5 | participant Delay as Delay block 6 | participant Delay.output as Delay.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>Delay: Connect 10 | Delay-->>BlockB: Connect 11 | 12 | loop Delay process 13 | BlockA->>Delay: Message 14 | Delay->>Delay: Sleep 15 | Delay->>BlockB: Message 16 | end 17 | 18 | BlockA-->>Delay: Disconnect 19 | Delay-->>Delay.input: Close 20 | Delay-->>Delay.output: Close 21 | Delay-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow/examples/echo_lines/README.md: -------------------------------------------------------------------------------- 1 | # Echo Lines Example 2 | 3 | This is a trivial two-block example program that reads bytes from standard 4 | input (stdin) and writes them to standard output (stdout). 5 | 6 | Since stdin and stdout are line buffered in the terminal, effectively this 7 | program ends up echoing lines of text without needing to decode/encode lines. 8 | 9 | ## Block Diagram 10 | 11 | ```mermaid 12 | block-beta 13 | columns 4 14 | ReadStdin space:2 WriteStdout 15 | ReadStdin-- "output → input" -->WriteStdout 16 | 17 | classDef block height:48px,padding:8px; 18 | classDef hidden visibility:none; 19 | class ReadStdin block 20 | class WriteStdout block 21 | ``` 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/decode.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant Decode.input as Decode.input port 5 | participant Decode as Decode block 6 | participant Decode.output as Decode.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>Decode: Connect 10 | Decode-->>BlockB: Connect 11 | 12 | loop Decode process 13 | BlockA->>Decode: Message (Bytes) 14 | Decode->>Decode: Decode message 15 | Decode->>BlockB: Message 16 | end 17 | 18 | BlockA-->>Decode: Disconnect 19 | Decode-->>Decode.input: Close 20 | Decode-->>Decode.output: Close 21 | Decode-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/encode.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant Encode.input as Encode.input port 5 | participant Encode as Encode block 6 | participant Encode.output as Encode.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>Encode: Connect 10 | Encode-->>BlockB: Connect 11 | 12 | loop Encode process 13 | BlockA->>Encode: Message 14 | Encode->>Encode: Encode message 15 | Encode->>BlockB: Message (Bytes) 16 | end 17 | 18 | BlockA-->>Encode: Disconnect 19 | Encode-->>Encode.input: Close 20 | Encode-->>Encode.output: Close 21 | Encode-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_env.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant ReadEnv.name as ReadEnv.name port 5 | participant ReadEnv as ReadEnv block 6 | participant ReadEnv.output as ReadEnv.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>ReadEnv: Connect 10 | 11 | BlockA->>ReadEnv: Message (e.g. "TERM") 12 | ReadEnv-->>ReadEnv.name: Close 13 | ReadEnv-->>BlockA: Disconnect 14 | ReadEnv-->>BlockB: Connect 15 | 16 | ReadEnv->>ReadEnv: Read environment variable 17 | ReadEnv->>BlockB: Message (e.g. "xterm-256color") 18 | 19 | ReadEnv-->>ReadEnv.output: Close 20 | ReadEnv-->>BlockB: Disconnect 21 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/block_runtime.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{Duration, Instant, Range}, 5 | BlockError, Port, 6 | }; 7 | 8 | pub trait BlockRuntime: Send + Sync { 9 | fn is_alive(&self) -> bool; 10 | 11 | fn sleep_for(&self, duration: Duration) -> Result<(), BlockError>; 12 | 13 | fn sleep_until(&self, instant: Instant) -> Result<(), BlockError>; // TODO 14 | 15 | /// Wait for a port to be connected. 16 | fn wait_for(&self, port: &dyn Port) -> Result<(), BlockError>; 17 | 18 | fn yield_now(&self) -> Result<(), BlockError>; 19 | 20 | fn random_duration(&self, range: Range) -> Duration; 21 | } 22 | -------------------------------------------------------------------------------- /lib/protoflow/examples/count_lines/main.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use protoflow::{blocks::*, BlockResult}; 4 | 5 | pub fn main() -> BlockResult { 6 | System::run(|s| { 7 | let stdin = s.read_stdin(); 8 | 9 | let line_decoder = s.decode_lines(); 10 | s.connect(&stdin.output, &line_decoder.input); 11 | 12 | let counter = s.count::(); 13 | s.connect(&line_decoder.output, &counter.input); 14 | 15 | let count_encoder = s.encode_lines(); 16 | s.connect(&counter.count, &count_encoder.input); 17 | 18 | let stdout = s.write_stdout(); 19 | s.connect(&count_encoder.output, &stdout.input); 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/transports/mpsc/output.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use super::MpscTransportEvent; 6 | use crate::PortState; 7 | use std::sync::mpsc::SyncSender; 8 | 9 | #[derive(Clone, Debug, Default)] 10 | pub enum MpscTransportOutputPortState { 11 | #[default] 12 | Open, 13 | Connected(SyncSender), 14 | Closed, 15 | } 16 | 17 | impl MpscTransportOutputPortState { 18 | pub fn state(&self) -> PortState { 19 | match self { 20 | Self::Open => PortState::Open, 21 | Self::Connected(_) => PortState::Connected, 22 | Self::Closed => PortState::Closed, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/transports/mpsc/input.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use super::MpscTransportEvent; 6 | use crate::PortState; 7 | use parking_lot::Mutex; 8 | use std::sync::mpsc::Receiver; 9 | 10 | #[derive(Debug, Default)] 11 | pub enum MpscTransportInputPortState { 12 | #[default] 13 | Open, 14 | Connected(Mutex>), 15 | Closed, 16 | } 17 | 18 | impl MpscTransportInputPortState { 19 | pub fn state(&self) -> PortState { 20 | match self { 21 | Self::Open => PortState::Open, 22 | Self::Connected(_) => PortState::Connected, 23 | Self::Closed => PortState::Closed, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_dir.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant ReadDir.path as ReadDir.path port 5 | participant ReadDir as ReadDir block 6 | participant ReadDir.output as ReadDir.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>ReadDir: Connect 10 | 11 | BlockA->>ReadDir: Message (e.g. "/tmp") 12 | ReadDir-->>ReadDir.path: Close 13 | ReadDir-->>BlockA: Disconnect 14 | ReadDir-->>BlockB: Connect 15 | 16 | loop ReadDir process 17 | ReadDir->>ReadDir: Read directory entries 18 | ReadDir->>BlockB: Message (e.g. "/tmp/file.txt") 19 | end 20 | 21 | ReadDir-->>ReadDir.output: Close 22 | ReadDir-->>BlockB: Disconnect 23 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/decode_csv.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant DecodeCSV.input as DecodeCSV.input port 5 | participant DecodeCSV as DecodeCSV block 6 | participant DecodeCSV.output as DecodeCSV.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>DecodeCSV: Connect 10 | DecodeCSV-->>BlockB: Connect 11 | 12 | loop DecodeCSV process 13 | BlockA->>DecodeCSV: Message 14 | DecodeCSV->>DecodeCSV: Decodes CSV file 15 | DecodeCSV->>BlockB: Message 16 | end 17 | 18 | BlockA-->>DecodeCSV: Disconnect 19 | DecodeCSV-->>DecodeCSV.input: Close 20 | DecodeCSV-->>DecodeCSV.output: Close 21 | DecodeCSV-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/encode_csv.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant EncodeCSV.input as EncodeCSV.input port 5 | participant EncodeCSV as EncodeCSV block 6 | participant EncodeCSV.output as EncodeCSV.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>EncodeCSV: Connect 10 | EncodeCSV-->>BlockB: Connect 11 | 12 | loop EncodeCSV process 13 | BlockA->>EncodeCSV: Message 14 | EncodeCSV->>EncodeCSV: Encodes CSV data 15 | EncodeCSV->>BlockB: Message 16 | end 17 | 18 | BlockA-->>EncodeCSV: Disconnect 19 | EncodeCSV-->>EncodeCSV.input: Close 20 | EncodeCSV-->>EncodeCSV.output: Close 21 | EncodeCSV-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/read_file.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant ReadFile.path as ReadFile.path port 5 | participant ReadFile as ReadFile block 6 | participant ReadFile.output as ReadFile.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>ReadFile: Connect 10 | 11 | BlockA->>ReadFile: Message (e.g. "/tmp/file.txt") 12 | ReadFile-->>ReadFile.path: Close 13 | ReadFile-->>BlockA: Disconnect 14 | ReadFile-->>BlockB: Connect 15 | 16 | loop ReadFile process 17 | ReadFile->>ReadFile: Read bytes from the file 18 | ReadFile->>BlockB: Message (Bytes) 19 | end 20 | 21 | ReadFile-->>ReadFile.output: Close 22 | ReadFile-->>BlockB: Disconnect 23 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/decode_hex.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant DecodeHex.input as DecodeHex.input port 5 | participant DecodeHex as DecodeHex block 6 | participant DecodeHex.output as DecodeHex.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>DecodeHex: Connect 10 | DecodeHex-->>BlockB: Connect 11 | 12 | loop DecodeHex process 13 | BlockA->>DecodeHex: Message (Bytes) 14 | DecodeHex->>DecodeHex: Decode from hexadecimal 15 | DecodeHex->>BlockB: Message (Bytes) 16 | end 17 | 18 | BlockA-->>DecodeHex: Disconnect 19 | DecodeHex-->>DecodeHex.input: Close 20 | DecodeHex-->>DecodeHex.output: Close 21 | DecodeHex-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/encode_hex.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant EncodeHex.input as EncodeHex.input port 5 | participant EncodeHex as EncodeHex block 6 | participant EncodeHex.output as EncodeHex.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>EncodeHex: Connect 10 | EncodeHex-->>BlockB: Connect 11 | 12 | loop EncodeHex process 13 | BlockA->>EncodeHex: Message (Bytes) 14 | EncodeHex->>EncodeHex: Encode into hexadecimal 15 | EncodeHex->>BlockB: Message (Bytes) 16 | end 17 | 18 | BlockA-->>EncodeHex: Disconnect 19 | EncodeHex-->>EncodeHex.input: Close 20 | EncodeHex-->>EncodeHex.output: Close 21 | EncodeHex-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/decode_json.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant DecodeJSON.input as DecodeJSON.input port 5 | participant DecodeJSON as DecodeJSON block 6 | participant DecodeJSON.output as DecodeJSON.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>DecodeJSON: Connect 10 | DecodeJSON-->>BlockB: Connect 11 | 12 | loop DecodeJSON process 13 | BlockA->>DecodeJSON: Message (Bytes) 14 | DecodeJSON->>DecodeJSON: Decode from JSON 15 | DecodeJSON->>BlockB: Message 16 | end 17 | 18 | BlockA-->>DecodeJSON: Disconnect 19 | DecodeJSON-->>DecodeJSON.input: Close 20 | DecodeJSON-->>DecodeJSON.output: Close 21 | DecodeJSON-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/io/encode_json.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant EncodeJSON.input as EncodeJSON.input port 5 | participant EncodeJSON as EncodeJSON block 6 | participant EncodeJSON.output as EncodeJSON.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>EncodeJSON: Connect 10 | EncodeJSON-->>BlockB: Connect 11 | 12 | loop EncodeJSON process 13 | BlockA->>EncodeJSON: Message 14 | EncodeJSON->>EncodeJSON: Encode into JSON 15 | EncodeJSON->>BlockB: Message (Bytes) 16 | end 17 | 18 | BlockA-->>EncodeJSON: Disconnect 19 | EncodeJSON-->>EncodeJSON.input: Close 20 | EncodeJSON-->>EncodeJSON.output: Close 21 | EncodeJSON-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/split_string.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant SplitString.input as SplitString.input port 5 | participant SplitString as SplitString block 6 | participant SplitString.output as SplitString.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>SplitString: Connect 10 | SplitString-->>BlockB: Connect 11 | 12 | loop SplitString process 13 | BlockA->>SplitString: Message 14 | SplitString->>SplitString: Split string 15 | SplitString->>BlockB: Message 16 | end 17 | 18 | BlockA-->>SplitString: Disconnect 19 | SplitString-->>SplitString.input: Close 20 | SplitString-->>SplitString.output: Close 21 | SplitString-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/sys/write_file.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant WriteFile.path as WriteFile.path port 5 | participant WriteFile as WriteFile block 6 | participant WriteFile.input as WriteFile.input port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>WriteFile: Connect 10 | 11 | BlockA->>WriteFile: Message (e.g. "/tmp/file.txt") 12 | WriteFile-->>WriteFile.path: Close 13 | WriteFile-->>BlockA: Disconnect 14 | 15 | BlockB-->>WriteFile: Connect 16 | 17 | loop WriteFile process 18 | BlockB->>WriteFile: Message (Bytes) 19 | WriteFile->>WriteFile: Write bytes to the file 20 | end 21 | 22 | BlockB-->>WriteFile: Disconnect 23 | WriteFile-->>WriteFile.input: Close 24 | -------------------------------------------------------------------------------- /lib/protoflow/tests/mpsc.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use protoflow::{ 4 | blocks::{Const, Drop}, 5 | runtimes::StdRuntime, 6 | transports::MpscTransport, 7 | System, SystemExecution, 8 | }; 9 | 10 | #[test] 11 | fn execute_mpsc_transport() -> Result<(), ()> { 12 | let transport = MpscTransport::new(); 13 | let runtime = StdRuntime::new(transport).unwrap(); 14 | let mut system = System::new(&runtime); 15 | let constant = system.block(Const { 16 | output: system.output(), 17 | value: 42, 18 | }); 19 | let blackhole = system.block(Drop::new(system.input())); 20 | system.connect(&constant.output, &blackhole.input); 21 | let process = SystemExecution::execute(system).unwrap(); 22 | process.join().unwrap(); 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/text/concat_strings.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant ConcatStrings.input as ConcatStrings.input port 5 | participant ConcatStrings as ConcatStrings block 6 | participant ConcatStrings.output as ConcatStrings.output port 7 | participant BlockB as Another block 8 | 9 | BlockA-->>ConcatStrings: Connect 10 | ConcatStrings-->>BlockB: Connect 11 | 12 | loop ConcatStrings process 13 | BlockA->>ConcatStrings: Message 14 | ConcatStrings->>ConcatStrings: Concat strings 15 | ConcatStrings->>BlockB: Message 16 | end 17 | 18 | BlockA-->>ConcatStrings: Disconnect 19 | ConcatStrings-->>ConcatStrings.input: Close 20 | ConcatStrings-->>ConcatStrings.output: Close 21 | ConcatStrings-->>BlockB: Disconnect 22 | -------------------------------------------------------------------------------- /lib/protoflow-syntax/src/analysis_error.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #[cfg(feature = "std")] 4 | extern crate std; 5 | 6 | use crate::prelude::String; 7 | use displaydoc::Display; 8 | use error_stack::Result; 9 | use sysml_model::QualifiedName; 10 | 11 | pub type AnalysisResult = Result; 12 | 13 | #[derive(Debug, Display)] 14 | pub enum AnalysisError { 15 | /// Parse failure 16 | ParseFailure, 17 | /// Invalid import: `{0}`. 18 | InvalidImport(QualifiedName), 19 | /// Unknown name: `{0}`. 20 | UnknownName(QualifiedName), 21 | /// Other error: `{0}`. 22 | Other(String), 23 | } 24 | 25 | #[cfg(feature = "std")] 26 | impl std::error::Error for AnalysisError {} 27 | 28 | #[cfg(not(feature = "std"))] 29 | impl error_stack::Context for AnalysisError {} 30 | -------------------------------------------------------------------------------- /lib/protoflow/src/feature.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | /// The set of features that are enabled in this build of the crate. 4 | pub static FEATURES: &[&str] = &[ 5 | #[cfg(feature = "beta")] 6 | "beta", 7 | #[cfg(feature = "blocks")] 8 | "blocks", 9 | #[cfg(feature = "crossbeam")] 10 | "crossbeam", 11 | #[cfg(feature = "derive")] 12 | "derive", 13 | #[cfg(feature = "flume")] 14 | "flume", 15 | #[cfg(feature = "rand")] 16 | "rand", 17 | #[cfg(feature = "serde")] 18 | "serde", 19 | #[cfg(feature = "syntax")] 20 | "syntax", 21 | #[cfg(feature = "sysml")] 22 | "sysml", 23 | #[cfg(feature = "tracing")] 24 | "tracing", 25 | #[cfg(feature = "web")] 26 | "web", 27 | #[cfg(feature = "zeromq")] 28 | "zeromq", 29 | ]; 30 | -------------------------------------------------------------------------------- /lib/protoflow-derive/src/meta.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use syn::Attribute; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum BlockFieldAttribute { 7 | Input, 8 | Output, 9 | Parameter, 10 | State, 11 | } 12 | 13 | impl TryFrom<&Attribute> for BlockFieldAttribute { 14 | type Error = (); 15 | 16 | fn try_from(attr: &Attribute) -> Result { 17 | let path = attr.path(); 18 | if path.is_ident("input") { 19 | Ok(Self::Input) 20 | } else if path.is_ident("output") { 21 | Ok(Self::Output) 22 | } else if path.is_ident("parameter") { 23 | Ok(Self::Parameter) 24 | } else if path.is_ident("state") { 25 | Ok(Self::State) 26 | } else { 27 | Err(()) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/protoflow/tests/zst.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use protoflow::{ 4 | blocks::Const, runtimes::StdRuntime, transports::MpscTransport, System, SystemExecution, 5 | }; 6 | 7 | #[test] 8 | fn const_with_numeric_zero() -> Result<(), ()> { 9 | let transport = MpscTransport::new(); 10 | let runtime = StdRuntime::new(transport).unwrap(); 11 | 12 | let mut system = System::new(&runtime); 13 | let constant: Const = system.block(Const { 14 | output: system.output(), 15 | value: 0, 16 | }); 17 | let output = system.input(); 18 | 19 | system.connect(&constant.output, &output); 20 | 21 | let process = SystemExecution::execute(system).unwrap(); 22 | 23 | assert_eq!(output.recv(), Ok(Some(0))); // not Ok(None) 24 | process.join().unwrap(); 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/block_connections.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use super::{prelude::Vec, InputPortName, OutputPortName}; 4 | 5 | /// A trait for defining the connections of a block instance. 6 | pub trait BlockConnections { 7 | fn input_connections(&self) -> Vec<(&'static str, Option)> { 8 | if cfg!(debug_assertions) { 9 | unimplemented!("BlockConnections::input_connections") // for debug builds only 10 | } else { 11 | Vec::new() 12 | } 13 | } 14 | 15 | fn output_connections(&self) -> Vec<(&'static str, Option)> { 16 | if cfg!(debug_assertions) { 17 | unimplemented!("BlockConnections::output_connections") // for debug builds only 18 | } else { 19 | Vec::new() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/message_buffer.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::{Bytes, VecDeque}; 4 | 5 | #[doc(hidden)] 6 | #[derive(Clone, Debug, Default)] 7 | pub struct MessageBuffer { 8 | pub messages: VecDeque, 9 | } 10 | 11 | impl MessageBuffer { 12 | pub fn new() -> Self { 13 | Self::default() 14 | } 15 | 16 | pub fn is_empty(&self) -> bool { 17 | self.messages.is_empty() 18 | } 19 | 20 | pub fn len(&self) -> usize { 21 | self.messages.len() 22 | } 23 | 24 | pub fn clear(&mut self) { 25 | self.messages.clear(); 26 | } 27 | 28 | pub fn push(&mut self, message: Bytes) { 29 | self.messages.push_back(message); 30 | } 31 | 32 | pub fn pop(&mut self) -> Option { 33 | self.messages.pop_front() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/hash/hash.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant Hash.input as Hash.input port 5 | participant Hash as Hash block 6 | participant Hash.output as Hash.output port 7 | participant BlockB as Another block 8 | participant Hash.hash as Hash.hash port 9 | participant BlockC as Another block 10 | 11 | BlockA-->>Hash: Connect 12 | Hash-->>BlockB: Connect 13 | 14 | loop Hash process 15 | BlockA->>Hash: Message (Bytes) 16 | Hash->>Hash: Update state 17 | Hash->>BlockB: Message (Bytes) 18 | end 19 | 20 | BlockA-->>Hash: Disconnect 21 | Hash-->>Hash.input: Close 22 | Hash-->>Hash.output: Close 23 | Hash-->>BlockB: Disconnect 24 | 25 | Hash-->>BlockC: Connect 26 | Hash->>BlockC: Hash 27 | Hash-->>Hash.hash: Close 28 | Hash-->>BlockC: Disconnect 29 | 30 | %% TODO 31 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/flow.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | pub mod flow { 4 | use super::{ 5 | prelude::{Cow, Named}, 6 | BlockConnections, BlockInstantiation, 7 | }; 8 | 9 | pub trait FlowBlocks {} 10 | 11 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 12 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 13 | pub enum FlowBlockTag {} 14 | 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | #[derive(Clone, Debug)] 17 | pub enum FlowBlockConfig {} 18 | 19 | impl Named for FlowBlockConfig { 20 | fn name(&self) -> Cow { 21 | unreachable!() 22 | } 23 | } 24 | 25 | impl BlockConnections for FlowBlockConfig {} 26 | 27 | impl BlockInstantiation for FlowBlockConfig {} 28 | } 29 | 30 | pub use flow::*; 31 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/math.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | pub mod math { 4 | use super::{ 5 | prelude::{Cow, Named}, 6 | BlockConnections, BlockInstantiation, 7 | }; 8 | 9 | pub trait MathBlocks {} 10 | 11 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 12 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 13 | pub enum MathBlockTag {} 14 | 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | #[derive(Clone, Debug)] 17 | pub enum MathBlockConfig {} 18 | 19 | impl Named for MathBlockConfig { 20 | fn name(&self) -> Cow { 21 | unreachable!() 22 | } 23 | } 24 | 25 | impl BlockConnections for MathBlockConfig {} 26 | 27 | impl BlockInstantiation for MathBlockConfig {} 28 | } 29 | 30 | pub use math::*; 31 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/doc/core/count.seq.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | autonumber 3 | participant BlockA as Another block 4 | participant Count.input as Count.input port 5 | participant Count as Count block 6 | participant Count.output as Count.output port 7 | participant BlockB as Another block 8 | participant Count.count as Count.count port 9 | participant BlockC as Another block 10 | 11 | BlockA-->>Count: Connect 12 | Count-->>BlockB: Connect 13 | 14 | loop Count process 15 | BlockA->>Count: Message 16 | Count->>Count: Increment counter 17 | Count->>BlockB: Message 18 | end 19 | 20 | BlockA-->>Count: Disconnect 21 | Count-->>Count.input: Close 22 | Count-->>Count.output: Close 23 | Count-->>BlockB: Disconnect 24 | 25 | Count-->>BlockC: Connect 26 | Count->>BlockC: Counter 27 | Count-->>Count.count: Close 28 | Count-->>BlockC: Disconnect 29 | 30 | %% TODO 31 | -------------------------------------------------------------------------------- /lib/protoflow-flume/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoflow-flume" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | description.workspace = true 8 | #documentation.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | license.workspace = true 13 | keywords.workspace = true 14 | categories.workspace = true 15 | publish.workspace = true 16 | 17 | [features] 18 | default = ["all", "std"] 19 | all = ["tracing"] 20 | std = ["protoflow-core/std", "tracing?/std"] 21 | tracing = ["protoflow-core/tracing", "dep:tracing"] 22 | unstable = ["protoflow-core/unstable"] 23 | 24 | [build-dependencies] 25 | cfg_aliases.workspace = true 26 | 27 | [dependencies] 28 | flume = { version = "0.11", default-features = false } 29 | protoflow-core.workspace = true 30 | tracing = { version = "0.1", default-features = false, optional = true } 31 | 32 | [dev-dependencies] 33 | -------------------------------------------------------------------------------- /lib/protoflow-zeromq/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoflow-zeromq" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | description.workspace = true 8 | #documentation.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | license.workspace = true 13 | keywords.workspace = true 14 | categories.workspace = true 15 | publish.workspace = true 16 | 17 | [features] 18 | default = ["all", "std"] 19 | all = ["tracing"] 20 | std = ["protoflow-core/std", "tracing?/std"] #, "zeromq/default"] 21 | tracing = ["protoflow-core/tracing", "dep:tracing"] 22 | unstable = ["protoflow-core/unstable"] 23 | 24 | [build-dependencies] 25 | cfg_aliases.workspace = true 26 | 27 | [dependencies] 28 | protoflow-core.workspace = true 29 | tracing = { version = "0.1", default-features = false, optional = true } 30 | #zeromq = { version = "0.4", default-features = false } 31 | 32 | [dev-dependencies] 33 | -------------------------------------------------------------------------------- /lib/protoflow-crossbeam/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoflow-crossbeam" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | description.workspace = true 8 | #documentation.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | license.workspace = true 13 | keywords.workspace = true 14 | categories.workspace = true 15 | publish.workspace = true 16 | 17 | [features] 18 | default = ["all", "std"] 19 | all = ["tracing"] 20 | std = ["crossbeam/std", "protoflow-core/std", "tracing?/std"] 21 | tracing = ["protoflow-core/tracing", "dep:tracing"] 22 | unstable = ["protoflow-core/unstable"] 23 | 24 | [build-dependencies] 25 | cfg_aliases.workspace = true 26 | 27 | [dependencies] 28 | crossbeam = { version = "0.8", default-features = false } 29 | protoflow-core.workspace = true 30 | tracing = { version = "0.1", default-features = false, optional = true } 31 | 32 | [dev-dependencies] 33 | -------------------------------------------------------------------------------- /lib/protoflow-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoflow-derive" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | description.workspace = true 8 | #documentation.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | license.workspace = true 13 | keywords = ["protoflow", "derive", "macro"] 14 | categories = ["development-tools::procedural-macro-helpers"] 15 | publish.workspace = true 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [features] 21 | default = ["all", "std"] 22 | all = ["sysml"] 23 | std = ["protoflow-core/std"] 24 | sysml = ["protoflow-core/sysml"] 25 | unstable = ["protoflow-core/unstable"] 26 | 27 | [dependencies] 28 | proc-macro2 = { version = "1", default-features = false } 29 | proc-macro-crate = "3.1" 30 | quote = { version = "1", default-features = false } 31 | syn = { version = "2", default-features = true } 32 | 33 | [dev-dependencies] 34 | protoflow-core.workspace = true 35 | -------------------------------------------------------------------------------- /lib/protoflow/examples/count_lines/README.md: -------------------------------------------------------------------------------- 1 | # Count Lines Example 2 | 3 | This is a simple five-block example program that reads bytes from standard 4 | input (stdin), decodes them into strings of newline-terminated lines, keeps a 5 | line count of the number of lines read, and after reading all input lines writes 6 | out that line count on standard output (stdout). 7 | 8 | Note that unlike the trivial [`echo_lines`](../echo_lines) example program, 9 | this program is not contingent on the terminal itself being line buffered. 10 | 11 | ## Block Diagram 12 | 13 | ```mermaid 14 | block-beta 15 | columns 13 16 | ReadStdin space:2 Decode space:2 Count space:2 Encode space:2 WriteStdout 17 | ReadStdin-- "output → input" -->Decode 18 | Decode-- "output → input" -->Count 19 | Count-- "count → input" -->Encode 20 | Encode-- "output → input" -->WriteStdout 21 | 22 | classDef block height:48px,padding:8px; 23 | classDef hidden visibility:none; 24 | class ReadStdin block 25 | class Decode block 26 | class Count block 27 | class Encode block 28 | class WriteStdout block 29 | ``` 30 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/tests/json_roundtrip.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use bytes::Bytes; 4 | use protoflow_blocks::{Const, IoBlocks, System, SystemBuilding, SystemExecution}; 5 | use protoflow_core::{runtimes::StdRuntime, transports::MpscTransport}; 6 | 7 | #[test] 8 | fn json_roundtrip() -> Result<(), ()> { 9 | let mut system = System::new(&StdRuntime::new(MpscTransport::new()).unwrap()); 10 | 11 | let input_bytes = Bytes::from(r#"[null,true,1,10.1,"hello!",{"1":false,"2":[1,2]}]"#); 12 | 13 | let input = system.block(Const::with_system(&system, input_bytes.clone())); 14 | let decode = system.decode_json(); 15 | let encode = system.encode_json(); 16 | let output = system.input(); 17 | 18 | system.connect(&input.output, &decode.input); 19 | system.connect(&decode.output, &encode.input); 20 | system.connect(&encode.output, &output); 21 | 22 | let process = system.execute().unwrap(); 23 | 24 | let message = output.recv().unwrap().unwrap(); 25 | 26 | process.join().unwrap(); 27 | 28 | assert_eq!(input_bytes, message); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /lib/protoflow/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![no_std] 4 | #![deny(unsafe_code)] 5 | 6 | extern crate self as protoflow; 7 | 8 | #[doc(hidden)] 9 | pub use protoflow_core::prelude; 10 | 11 | pub use protoflow_core::*; 12 | 13 | /// Default blocks are available if the crate was built with a 14 | /// `features = ["blocks"]` configuration. 15 | #[cfg(feature = "blocks")] 16 | //#[cfg_attr(docsrs, doc(cfg(feature = "blocks")))] 17 | pub use protoflow_blocks as blocks; 18 | 19 | /// Derive macros are available if the crate was built with a 20 | /// `features = ["derive"]` configuration. 21 | #[cfg(feature = "derive")] 22 | //#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] 23 | pub use protoflow_derive as derive; 24 | 25 | mod feature; 26 | pub use feature::*; 27 | 28 | /// The parser is available if the crate was built with a 29 | /// `features = ["syntax"]` configuration. 30 | #[cfg(feature = "syntax")] 31 | //#[cfg_attr(docsrs, doc(cfg(feature = "syntax")))] 32 | pub use protoflow_syntax as syntax; 33 | 34 | #[doc = include_str!("../../../README.md")] 35 | #[cfg(doctest)] 36 | pub struct ReadmeDoctests; 37 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/message_receiver.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | //! Common methods for receiving messages. 4 | 5 | use crate::{prelude::ToString, Message, PortError, PortResult}; 6 | 7 | pub trait MessageReceiver { 8 | /// Receives a message, blocking until one is available. 9 | /// 10 | /// Returns `Ok(Some(message))` if a message was received. 11 | /// Returns `Ok(None)` if the port is closed or disconnected. 12 | /// Returns `Err(PortError)` if an error occurs. 13 | fn recv(&self) -> PortResult> { 14 | Err(PortError::Other("not implemented".to_string())) 15 | } 16 | 17 | /// Tries to receive a message, returning immediately. 18 | /// 19 | /// Returns `Ok(Some(message))` if a message was received. 20 | /// Returns `Ok(None)` if no message was immediately available. 21 | /// Returns `Err(PortError::Disconnected)` if the port is disconnected. 22 | /// Returns `Err(PortError::Closed)` if the port is closed. 23 | /// Returns `Err(PortError)` if another error occurs. 24 | fn try_recv(&self) -> PortResult> { 25 | Err(PortError::Other("not implemented".to_string())) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/async_block.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{prelude::Box, BlockDescriptor, BlockHooks, BlockResult, BlockRuntime}; 4 | 5 | use async_trait::async_trait; 6 | use core::fmt; 7 | 8 | /// An async version of a regular block. 9 | #[async_trait] 10 | pub trait AsyncBlock: BlockDescriptor + BlockHooks + Send + Sync { 11 | /// Prepares this block for execution. 12 | /// 13 | /// This is called once before the first call to `execute`. 14 | /// This is where to open ports and allocate resources. 15 | fn prepare(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 16 | Ok(()) 17 | } 18 | 19 | /// Executes this block's computation asynchronously. 20 | async fn execute_async(&mut self, runtime: &dyn BlockRuntime) -> BlockResult; 21 | } 22 | 23 | impl fmt::Debug for dyn AsyncBlock { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | f.debug_struct("AsyncBlock") 26 | //.field("name", &self.name()) 27 | //.field("label", &self.label()) 28 | .field("inputs", &self.inputs()) 29 | .field("outputs", &self.outputs()) 30 | .field("parameters", &self.parameters()) 31 | .finish() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/port_state.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 4 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 5 | #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] 6 | pub enum PortState { 7 | #[default] 8 | Closed, 9 | Open, 10 | Connected, 11 | } 12 | 13 | impl PortState { 14 | /// Checks whether the port state is currently closed. 15 | pub fn is_closed(&self) -> bool { 16 | *self == PortState::Closed 17 | } 18 | 19 | /// Checks whether the port state is currently open. 20 | pub fn is_open(&self) -> bool { 21 | *self == PortState::Open 22 | } 23 | 24 | /// Checks whether the port state is currently connected. 25 | pub fn is_connected(&self) -> bool { 26 | *self == PortState::Connected 27 | } 28 | 29 | pub fn to_str(&self) -> &str { 30 | use PortState::*; 31 | match self { 32 | Closed => "closed", 33 | Open => "open", 34 | Connected => "connected", 35 | } 36 | } 37 | } 38 | 39 | impl AsRef for PortState { 40 | fn as_ref(&self) -> &str { 41 | self.to_str() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/message.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::{Bytes, String, Vec}; 4 | 5 | pub trait Message: prost::Message + Clone + Default {} 6 | 7 | impl Message for bool {} // google.protobuf.BoolValue 8 | impl Message for u32 {} // google.protobuf.UInt32Value 9 | impl Message for u64 {} // google.protobuf.UInt64Value 10 | impl Message for i32 {} // google.protobuf.Int32Value 11 | impl Message for i64 {} // google.protobuf.Int64Value 12 | impl Message for f32 {} // google.protobuf.FloatValue 13 | impl Message for f64 {} // google.protobuf.DoubleValue 14 | impl Message for String {} // google.protobuf.StringValue 15 | impl Message for Vec {} // google.protobuf.BytesValue 16 | impl Message for Bytes {} // google.protobuf.BytesValue 17 | impl Message for () {} // google.protobuf.Empty 18 | 19 | impl Message for prost_types::Any {} // google.protobuf.Any 20 | impl Message for prost_types::Duration {} // google.protobuf.Duration 21 | impl Message for prost_types::ListValue {} // google.protobuf.ListValue 22 | impl Message for prost_types::Option {} // google.protobuf.Option 23 | impl Message for prost_types::Struct {} // google.protobuf.Struct 24 | impl Message for prost_types::Timestamp {} // google.protobuf.Timestamp 25 | impl Message for prost_types::Value {} // google.protobuf.Value 26 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/parameter_descriptor.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::{Cow, MaybeLabeled, Named, String}; 4 | 5 | /// A descriptor for a block parameter. 6 | #[derive(Clone, Default, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 7 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct ParameterDescriptor { 9 | /// The machine-readable name of this parameter. 10 | pub name: String, 11 | 12 | /// A human-readable label, if any, for this parameter. 13 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 14 | pub label: Option, 15 | 16 | /// The data type, if known, of this parameter. 17 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 18 | pub r#type: Option, 19 | 20 | /// A default value, if any, for this parameter. 21 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 22 | pub default_value: Option, 23 | } 24 | 25 | impl Named for ParameterDescriptor { 26 | fn name(&self) -> Cow { 27 | Cow::Borrowed(&self.name) 28 | } 29 | } 30 | 31 | impl MaybeLabeled for ParameterDescriptor { 32 | fn label(&self) -> Option> { 33 | self.label.as_deref().map(Cow::Borrowed) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/message_sender.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | //! Common methods for sending messages. 4 | 5 | use crate::{prelude::ToString, Message, PortError, PortResult}; 6 | 7 | pub trait MessageSender { 8 | /// Sends a message, blocking until it has been sent. 9 | /// 10 | /// Returns `Ok(())` if the message was sent. 11 | /// Returns `Err(PortError::Disconnected)` if the port is disconnected. 12 | /// Returns `Err(PortError::Closed)` if the port is closed. 13 | /// Returns `Err(PortError)` if another error occurs. 14 | fn send<'a>(&self, _message: impl Into<&'a T>) -> PortResult<()> 15 | where 16 | T: 'a, 17 | { 18 | Err(PortError::Other("not implemented".to_string())) 19 | } 20 | 21 | /// Tries to send a message, returning immediately. 22 | /// 23 | /// Returns `Ok(true)` if the message was sent. 24 | /// Returns `Ok(false)` if the message could not be immediately sent. 25 | /// Returns `Err(PortError::Disconnected)` if the port is disconnected. 26 | /// Returns `Err(PortError::Closed)` if the port is closed. 27 | /// Returns `Err(PortError)` if another error occurs. 28 | fn try_send<'a>(&self, _message: impl Into<&'a T>) -> PortResult 29 | where 30 | T: 'a, 31 | { 32 | Err(PortError::Other("not implemented".to_string())) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #[cfg(feature = "std")] 4 | extern crate std; 5 | 6 | #[cfg(not(feature = "std"))] 7 | extern crate alloc; 8 | 9 | #[cfg(feature = "std")] 10 | use std as alloc; 11 | 12 | #[allow(unused)] 13 | pub use alloc::{ 14 | borrow::Cow, 15 | boxed::Box, 16 | collections::btree_set::Iter as BTreeSetIter, 17 | collections::{BTreeMap, BTreeSet, VecDeque}, 18 | format, 19 | rc::Rc, 20 | string::{String, ToString}, 21 | sync::Arc, 22 | vec, 23 | vec::Vec, 24 | }; 25 | 26 | #[allow(unused)] 27 | pub use core::{ 28 | any::type_name, 29 | cell::RefCell, 30 | convert::{AsRef, TryFrom}, 31 | fmt, 32 | marker::PhantomData, 33 | ops::{Deref, Index, Range}, 34 | option::Option, 35 | result::Result, 36 | slice, 37 | str::FromStr, 38 | sync::atomic::{AtomicBool, AtomicUsize, Ordering}, 39 | time::Duration, 40 | }; 41 | 42 | pub use bytes::{Bytes, BytesMut}; 43 | 44 | pub type Instant = Duration; 45 | 46 | #[doc(hidden)] 47 | pub use bytes; 48 | 49 | #[doc(hidden)] 50 | pub use parking_lot::RwLock; 51 | 52 | #[doc(hidden)] 53 | pub use prost; 54 | 55 | #[doc(hidden)] 56 | pub use prost_types; 57 | 58 | #[cfg(feature = "sysml")] 59 | #[doc(hidden)] 60 | pub use sysml_model; 61 | 62 | pub use dogma::traits::{Labeled, MaybeLabeled, MaybeNamed, Named}; 63 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/types/encoding.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::{fmt, FromStr, String}; 4 | 5 | /// The encoding to use when (de)serializing messages from/to bytes. 6 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 7 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 8 | pub enum Encoding { 9 | #[default] 10 | ProtobufWithLengthPrefix, 11 | ProtobufWithoutLengthPrefix, 12 | TextWithNewlineSuffix, 13 | } 14 | 15 | impl FromStr for Encoding { 16 | type Err = String; 17 | 18 | fn from_str(input: &str) -> Result { 19 | use Encoding::*; 20 | Ok(match input { 21 | "protobuf-with-length-prefix" | "protobuf" => ProtobufWithLengthPrefix, 22 | "protobuf-without-length-prefix" => ProtobufWithoutLengthPrefix, 23 | "text-with-newline-suffix" | "text" => TextWithNewlineSuffix, 24 | _ => return Err(String::from(input)), 25 | }) 26 | } 27 | } 28 | 29 | impl fmt::Display for Encoding { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | use Encoding::*; 32 | match self { 33 | ProtobufWithLengthPrefix => write!(f, "protobuf-with-length-prefix"), 34 | ProtobufWithoutLengthPrefix => write!(f, "protobuf-without-length-prefix"), 35 | TextWithNewlineSuffix => write!(f, "text-with-newline-suffix"), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/protoflow-derive/src/derives/system.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::util::protoflow_crate; 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | use syn::{self, Data, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Result}; 7 | 8 | pub(crate) fn expand_derive_system(input: &DeriveInput) -> Result { 9 | let protoflow = protoflow_crate(); 10 | let ident = &input.ident; 11 | let generics = &input.generics; 12 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 13 | let _fields = match &input.data { 14 | Data::Struct(DataStruct { 15 | fields: Fields::Named(FieldsNamed { named: fields, .. }), 16 | .. 17 | }) => fields.into_iter().collect(), 18 | Data::Struct(DataStruct { 19 | fields: 20 | Fields::Unnamed(FieldsUnnamed { 21 | unnamed: fields, .. 22 | }), 23 | .. 24 | }) => fields.into_iter().collect(), 25 | Data::Struct(DataStruct { 26 | fields: Fields::Unit, 27 | .. 28 | }) => Vec::new(), 29 | _ => panic!("`#[derive(System)]` only supports structs"), 30 | }; 31 | 32 | Ok(quote! { 33 | #[automatically_derived] 34 | #[allow( 35 | unused_qualifications, 36 | clippy::redundant_locals, 37 | )] 38 | impl #impl_generics #protoflow::System for #ident #ty_generics #where_clause {} 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/types/delay_type.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::{Duration, FromStr, Range, String}; 4 | 5 | /// The type of delay (fixed or random) to apply to message relay. 6 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 7 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 8 | pub enum DelayType { 9 | #[cfg_attr( 10 | feature = "serde", 11 | serde(deserialize_with = "duration_str::deserialize_duration") 12 | )] 13 | Fixed(Duration), 14 | 15 | Random(Range), 16 | } 17 | 18 | impl Default for DelayType { 19 | fn default() -> Self { 20 | Self::Fixed(Duration::from_secs(1)) 21 | } 22 | } 23 | 24 | impl FromStr for DelayType { 25 | type Err = InvalidDelayType; 26 | 27 | fn from_str(input: &str) -> Result { 28 | // TODO: parse random range parameters as well 29 | Ok(match input.trim() { 30 | "" => Self::default(), 31 | "random" | "rand" => Self::Random(Range { 32 | start: Duration::from_secs_f64(0.), 33 | end: Duration::from_secs_f64(1.), 34 | }), 35 | input => duration_str::parse_std(input).map(Self::Fixed)?, 36 | }) 37 | } 38 | } 39 | 40 | #[derive(Clone, Debug, Eq, PartialEq)] 41 | pub enum InvalidDelayType { 42 | InvalidDuration(String), 43 | } 44 | 45 | impl From for InvalidDelayType { 46 | fn from(input: String) -> Self { 47 | InvalidDelayType::InvalidDuration(input) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/block_error.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{fmt, Box, Result, String, ToString}, 5 | PortError, 6 | }; 7 | 8 | #[cfg(feature = "std")] 9 | extern crate std; 10 | 11 | pub type BlockResult = Result; 12 | 13 | #[derive(Debug)] 14 | pub enum BlockError { 15 | Terminated, 16 | PortError(PortError), 17 | Other(String), 18 | #[cfg(feature = "std")] 19 | Panic(Box), 20 | } 21 | 22 | impl fmt::Display for BlockError { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | match self { 25 | Self::Terminated => write!(f, "Execution terminated"), 26 | Self::PortError(e) => write!(f, "{}", e), 27 | Self::Other(message) => write!(f, "{}", message), 28 | #[cfg(feature = "std")] 29 | Self::Panic(e) => write!(f, "Panic: {:?}", e), 30 | } 31 | } 32 | } 33 | 34 | #[cfg(feature = "std")] 35 | impl std::error::Error for BlockError {} 36 | 37 | #[cfg(feature = "std")] 38 | impl From for BlockError { 39 | fn from(error: std::io::Error) -> Self { 40 | Self::Other(error.to_string()) 41 | } 42 | } 43 | 44 | #[cfg(feature = "std")] 45 | impl From> for BlockError { 46 | fn from(error: Box) -> Self { 47 | Self::Panic(error) 48 | } 49 | } 50 | 51 | impl From for BlockError { 52 | fn from(error: PortError) -> Self { 53 | Self::PortError(error) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/types/byte_size.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::{fmt, FromStr}; 4 | 5 | /// A byte size value. 6 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 7 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct ByteSize(ubyte::ByteUnit); 9 | 10 | impl ByteSize { 11 | pub const fn new(value: u64) -> Self { 12 | Self(ubyte::ByteUnit::Byte(value)) 13 | } 14 | 15 | pub const fn as_u64(self) -> u64 { 16 | self.0.as_u64() 17 | } 18 | 19 | pub const fn as_usize(self) -> usize { 20 | self.0.as_u64() as _ 21 | } 22 | } 23 | 24 | impl Into for ByteSize { 25 | fn into(self) -> u64 { 26 | self.as_u64() 27 | } 28 | } 29 | 30 | impl Into for ByteSize { 31 | fn into(self) -> usize { 32 | self.as_usize() 33 | } 34 | } 35 | 36 | impl From for ByteSize { 37 | fn from(value: u64) -> Self { 38 | Self::new(value) 39 | } 40 | } 41 | 42 | impl From for ByteSize { 43 | fn from(value: usize) -> Self { 44 | Self::new(value as _) 45 | } 46 | } 47 | 48 | impl FromStr for ByteSize { 49 | type Err = InvalidByteSize; 50 | 51 | fn from_str(input: &str) -> Result { 52 | FromStr::from_str(input).map(ByteSize) 53 | } 54 | } 55 | 56 | impl fmt::Display for ByteSize { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | fmt::Display::fmt(&self.0, f) 59 | } 60 | } 61 | 62 | pub type InvalidByteSize = ubyte::Error; 63 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/port_error.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{fmt, String, ToString}, 5 | DecodeError, PortID, 6 | }; 7 | 8 | #[cfg(feature = "std")] 9 | extern crate std; 10 | 11 | pub type PortResult = Result; 12 | 13 | #[derive(Clone, Debug, Eq, PartialEq)] 14 | pub enum PortError { 15 | Invalid(PortID), 16 | Closed, 17 | Disconnected, 18 | RecvFailed, 19 | SendFailed, 20 | DecodeFailed(DecodeError), 21 | Other(String), 22 | } 23 | 24 | impl fmt::Display for PortError { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | match self { 27 | Self::Invalid(port) => write!(f, "Port #{} is invalid", port), 28 | Self::Closed => write!(f, "Port is closed"), 29 | Self::Disconnected => write!(f, "Port is not connected"), 30 | Self::RecvFailed => write!(f, "Port receive failed"), 31 | Self::SendFailed => write!(f, "Port send failed"), 32 | Self::DecodeFailed(error) => write!(f, "Port decode failed: {}", error), 33 | Self::Other(message) => write!(f, "{}", message), 34 | } 35 | } 36 | } 37 | 38 | #[cfg(feature = "std")] 39 | impl std::error::Error for PortError {} 40 | 41 | #[cfg(feature = "std")] 42 | impl From for PortError { 43 | fn from(error: std::io::Error) -> Self { 44 | Self::Other(error.to_string()) 45 | } 46 | } 47 | 48 | impl From for PortError { 49 | fn from(error: DecodeError) -> Self { 50 | Self::DecodeFailed(error) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/utils/rw_condvar.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![allow(unused)] 4 | 5 | use crate::prelude::fmt; 6 | use parking_lot::{Condvar, Mutex, RwLockReadGuard}; 7 | 8 | // See: https://github.com/Amanieu/parking_lot/issues/165 9 | #[derive(Default)] 10 | pub struct RwCondvar { 11 | mutex: Mutex<()>, 12 | condvar: Condvar, 13 | } 14 | 15 | impl RwCondvar { 16 | /// Creates a new condition variable which is ready to be waited on and 17 | /// notified. 18 | pub const fn new() -> Self { 19 | Self { 20 | mutex: Mutex::new(()), 21 | condvar: Condvar::new(), 22 | } 23 | } 24 | 25 | /// Wakes up one blocked thread on this condvar. 26 | #[inline] 27 | pub fn notify_one(&self) -> bool { 28 | self.condvar.notify_one() 29 | } 30 | 31 | /// Wakes up all blocked threads on this condvar. 32 | #[inline] 33 | pub fn notify_all(&self) -> usize { 34 | self.condvar.notify_all() 35 | } 36 | 37 | /// Blocks the current thread until this condition variable receives a 38 | /// notification. 39 | pub fn wait(&self, rwlock_read_guard: &mut RwLockReadGuard<'_, T>) { 40 | let mutex_guard = self.mutex.lock(); 41 | RwLockReadGuard::unlocked(rwlock_read_guard, || { 42 | // Move the guard in to unlock it before we re-lock `rwlock_read_guard`: 43 | let mut mutex_guard = mutex_guard; 44 | self.condvar.wait(&mut mutex_guard); 45 | }); 46 | } 47 | } 48 | 49 | impl fmt::Debug for RwCondvar { 50 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 51 | f.pad("RwCondvar { .. }") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/transport.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{prelude::Bytes, InputPortID, OutputPortID, PortID, PortResult, PortState}; 4 | 5 | #[allow(unused)] 6 | pub trait Transport: AsTransport + Send + Sync { 7 | fn state(&self, port: PortID) -> PortResult { 8 | match port { 9 | PortID::Input(input) => self.input_state(input), 10 | PortID::Output(output) => self.output_state(output), 11 | } 12 | } 13 | 14 | fn input_state(&self, port: InputPortID) -> PortResult; 15 | fn output_state(&self, port: OutputPortID) -> PortResult; 16 | 17 | fn open_input(&self) -> PortResult; 18 | fn open_output(&self) -> PortResult; 19 | 20 | fn close(&self, port: PortID) -> PortResult { 21 | Ok(match port { 22 | PortID::Input(input) => self.close_input(input)?, 23 | PortID::Output(output) => self.close_output(output)?, 24 | }) 25 | } 26 | 27 | fn close_input(&self, input: InputPortID) -> PortResult; 28 | fn close_output(&self, output: OutputPortID) -> PortResult; 29 | fn connect(&self, source: OutputPortID, target: InputPortID) -> PortResult; 30 | fn send(&self, output: OutputPortID, message: Bytes) -> PortResult<()>; 31 | fn recv(&self, input: InputPortID) -> PortResult>; 32 | fn try_recv(&self, input: InputPortID) -> PortResult>; 33 | } 34 | 35 | pub trait AsTransport { 36 | fn as_transport(&self) -> &dyn Transport; 37 | } 38 | 39 | impl AsTransport for T { 40 | fn as_transport(&self) -> &dyn Transport { 41 | self 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/protoflow-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | //! This crate provides Protoflow's derive macros. 4 | //! 5 | //! ```edition2021 6 | //! # use protoflow_derive::{Block, FunctionBlock, Subsystem, System}; 7 | //! ``` 8 | 9 | #![deny(unsafe_code)] 10 | 11 | extern crate proc_macro; 12 | 13 | mod derives; 14 | mod meta; 15 | pub(crate) mod util; 16 | 17 | use proc_macro::TokenStream; 18 | use syn::{parse_macro_input, DeriveInput}; 19 | 20 | #[proc_macro_derive(Block, attributes(input, output, parameter, state))] 21 | pub fn derive_block(input: TokenStream) -> TokenStream { 22 | let input: DeriveInput = parse_macro_input!(input); 23 | derives::expand_derive_block(&input) 24 | .unwrap_or_else(syn::Error::into_compile_error) 25 | .into() 26 | } 27 | 28 | #[proc_macro_derive(FunctionBlock, attributes())] 29 | pub fn derive_function_block(input: TokenStream) -> TokenStream { 30 | let input: DeriveInput = parse_macro_input!(input); 31 | derives::expand_derive_function_block(&input) 32 | .unwrap_or_else(syn::Error::into_compile_error) 33 | .into() 34 | } 35 | 36 | #[proc_macro_derive(Subsystem, attributes(block))] 37 | pub fn derive_subsystem(input: TokenStream) -> TokenStream { 38 | let input: DeriveInput = parse_macro_input!(input); 39 | derives::expand_derive_system(&input) 40 | .unwrap_or_else(syn::Error::into_compile_error) 41 | .into() 42 | } 43 | 44 | #[proc_macro_derive(System, attributes(block))] 45 | pub fn derive_system(input: TokenStream) -> TokenStream { 46 | let input: DeriveInput = parse_macro_input!(input); 47 | derives::expand_derive_system(&input) 48 | .unwrap_or_else(syn::Error::into_compile_error) 49 | .into() 50 | } 51 | -------------------------------------------------------------------------------- /lib/protoflow-syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoflow-syntax" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | description.workspace = true 8 | #documentation.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | license.workspace = true 13 | keywords.workspace = true 14 | categories.workspace = true 15 | publish.workspace = true 16 | 17 | [features] 18 | default = ["all", "std"] 19 | all = ["sysml", "tracing"] 20 | std = [ 21 | "protoflow-blocks/std", 22 | "protoflow-core/std", 23 | "sysml-model?/std", 24 | "sysml-parser?/std", 25 | "tracing?/std", 26 | ] 27 | sysml = [ 28 | "protoflow-blocks/sysml", 29 | "protoflow-core/sysml", 30 | "dep:sysml-model", 31 | "dep:sysml-parser", 32 | ] 33 | tracing = [ 34 | "protoflow-blocks/tracing", 35 | "protoflow-core/tracing", 36 | "sysml-parser?/tracing", 37 | "dep:tracing", 38 | ] 39 | unstable = ["protoflow-blocks/unstable", "protoflow-core/unstable"] 40 | 41 | [build-dependencies] 42 | cfg_aliases.workspace = true 43 | 44 | [dependencies] 45 | displaydoc = { version = "0.2", default-features = false } 46 | error-stack = { version = "0.5", default-features = false } 47 | prettyplease = "0.2" 48 | proc-macro2 = { version = "1", default-features = false } 49 | protoflow-blocks.workspace = true 50 | protoflow-core.workspace = true 51 | quote = { version = "1", default-features = false } 52 | syn = { version = "2", default-features = true } 53 | sysml-model = { version = "=0.2.3", default-features = false, optional = true } 54 | sysml-parser = { version = "=0.2.3", default-features = false, features = [ 55 | "error-stack", 56 | ], optional = true } 57 | tracing = { version = "0.1", default-features = false, optional = true } 58 | 59 | [dev-dependencies] 60 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/port.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{fmt, MaybeLabeled, MaybeNamed, ToString}, 5 | PortError, PortID, PortResult, PortState, 6 | }; 7 | 8 | /// The common interface for ports, whether for input or output. 9 | pub trait Port: MaybeNamed + MaybeLabeled { 10 | /// A unique identifier for this port. 11 | fn id(&self) -> PortID; 12 | 13 | /// The current state of this port. 14 | fn state(&self) -> PortState; 15 | 16 | /// Checks whether this port is currently closed. 17 | fn is_closed(&self) -> bool { 18 | self.state().is_closed() 19 | } 20 | 21 | /// Checks whether this port is currently open. 22 | fn is_open(&self) -> bool { 23 | self.state().is_open() 24 | } 25 | 26 | /// Checks whether this port is currently connected. 27 | fn is_connected(&self) -> bool { 28 | self.state().is_connected() 29 | } 30 | 31 | /// Closes this port, returning immediately. 32 | /// 33 | /// If the port had an open connection, it will be disconnected. 34 | /// If the port was already closed, no further action is taken. 35 | /// There is no facility to reopen a port once it has been closed. 36 | /// 37 | /// Returns `Ok(true)` if the port was successfully closed. 38 | /// Returns `Ok(false)` if the port was already closed. 39 | /// Returns `Err(PortError)` if an error occurs. 40 | fn close(&mut self) -> PortResult { 41 | Err(PortError::Other("not implemented".to_string())) 42 | } 43 | } 44 | 45 | impl fmt::Debug for &dyn Port { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | f.debug_struct("Port") 48 | .field("id", &self.id()) 49 | .field("name", &self.name()) 50 | .field("state", &self.state()) 51 | .finish() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # See: https://doc.rust-lang.org/cargo/reference/manifest.html 2 | 3 | [workspace] 4 | members = ["lib/*"] 5 | default-members = ["lib/*"] 6 | resolver = "2" 7 | 8 | [workspace.package] 9 | version = "0.4.3" 10 | authors = ["Arto Bendiken"] 11 | edition = "2021" 12 | rust-version = "1.70" 13 | description = "Protoflow implements flow-based programming (FBP) for Rust using Protocol Buffers messages." 14 | #documentation = "https://docs.rs/protoflow/" 15 | readme = true 16 | homepage = "https://protoflow.rs" 17 | repository = "https://github.com/asimov-platform/protoflow" 18 | license = "Unlicense" 19 | keywords = ["protoflow", "protobuf", "flow", "fbp"] 20 | categories = ["concurrency", "network-programming", "no-std"] 21 | publish = true 22 | 23 | [workspace.dependencies] 24 | cfg_aliases = "0.2" 25 | protoflow = { version = "=0.4.3", default-features = false } 26 | protoflow-blocks = { version = "=0.4.3", default-features = false } 27 | protoflow-core = { version = "=0.4.3", default-features = false } 28 | protoflow-crossbeam = { version = "=0.4.3", default-features = false } 29 | protoflow-derive = { version = "=0.4.3", default-features = false } 30 | protoflow-flume = { version = "=0.4.3", default-features = false } 31 | protoflow-syntax = { version = "=0.4.3", default-features = false } 32 | protoflow-zeromq = { version = "=0.4.3", default-features = false } 33 | shadow-rs = { version = "0.26", features = ["tzdb"], default-features = false } 34 | 35 | [patch.crates-io] 36 | protoflow = { path = "lib/protoflow" } 37 | protoflow-blocks = { path = "lib/protoflow-blocks" } 38 | protoflow-core = { path = "lib/protoflow-core" } 39 | protoflow-crossbeam = { path = "lib/protoflow-crossbeam" } 40 | protoflow-derive = { path = "lib/protoflow-derive" } 41 | protoflow-flume = { path = "lib/protoflow-flume" } 42 | protoflow-syntax = { path = "lib/protoflow-syntax" } 43 | protoflow-zeromq = { path = "lib/protoflow-zeromq" } 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # See: https://docs.github.com/en/actions/writing-workflows 2 | --- 3 | name: Release 4 | 5 | # Trigger on any tag creation: 6 | on: 7 | push: 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: true 15 | matrix: 16 | include: 17 | - os: ubuntu-latest 18 | artifact: linux-x86 19 | target: x86_64-unknown-linux-gnu 20 | strip: true 21 | - os: ubuntu-latest 22 | artifact: linux-arm 23 | target: aarch64-unknown-linux-gnu 24 | use-zigbuild: true 25 | - os: ubuntu-latest 26 | artifact: macos-x86 27 | target: x86_64-apple-darwin 28 | use-zigbuild: true 29 | - os: ubuntu-latest 30 | artifact: macos-arm 31 | target: aarch64-apple-darwin 32 | use-zigbuild: true 33 | - os: ubuntu-latest 34 | artifact: windows-x64 35 | target: x86_64-pc-windows-gnu 36 | extension: exe 37 | name: Build ${{ matrix.artifact }} 38 | runs-on: ${{ matrix.os }} 39 | continue-on-error: false 40 | steps: 41 | - name: Build 42 | uses: asimov-platform/build-rust-action@v1 43 | with: 44 | target: ${{ matrix.target }} 45 | artifact-name: ${{ matrix.artifact }} 46 | binary-extension: ${{ matrix.extension }} 47 | strip-artifact: ${{ matrix.strip || 'false' }} 48 | use-zigbuild: ${{ matrix.use-zigbuild || 'false' }} 49 | rust-toolchain: 1.81.0 50 | 51 | release: 52 | name: Release 53 | runs-on: ubuntu-latest 54 | if: startsWith(github.ref, 'refs/tags/') 55 | needs: build 56 | permissions: 57 | contents: write 58 | steps: 59 | - name: Download artifacts 60 | uses: asimov-platform/release-action@v1 61 | with: 62 | changelog-path: CHANGES.md 63 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/block_descriptor.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{vec, MaybeLabeled, MaybeNamed, Vec}, 5 | ParameterDescriptor, PortDescriptor, 6 | }; 7 | 8 | /// A block is an autonomous unit of computation in a system. 9 | pub trait BlockDescriptor: AsBlockDescriptor + MaybeNamed + MaybeLabeled { 10 | /// A description of this block's I/O ports. 11 | fn ports(&self) -> Vec { 12 | let mut result = self.inputs(); 13 | result.append(&mut self.outputs()); 14 | result 15 | } 16 | 17 | /// A description of this block's input ports. 18 | fn inputs(&self) -> Vec { 19 | vec![] 20 | } 21 | 22 | /// A description of this block's output ports. 23 | fn outputs(&self) -> Vec { 24 | vec![] 25 | } 26 | 27 | /// A description of this block's parameters. 28 | fn parameters(&self) -> Vec { 29 | vec![] 30 | } 31 | } 32 | 33 | pub trait AsBlockDescriptor { 34 | fn as_block_descriptor(&self) -> &dyn BlockDescriptor; 35 | } 36 | 37 | impl AsBlockDescriptor for T { 38 | fn as_block_descriptor(&self) -> &dyn BlockDescriptor { 39 | self 40 | } 41 | } 42 | 43 | #[cfg(feature = "serde")] 44 | impl serde::Serialize for &dyn BlockDescriptor { 45 | fn serialize(&self, serializer: S) -> Result 46 | where 47 | S: serde::Serializer, 48 | { 49 | use serde::ser::SerializeStruct; 50 | let mut state = serializer.serialize_struct("BlockDescriptor", 5)?; 51 | state.serialize_field("name", &self.name())?; 52 | state.serialize_field("label", &self.label())?; 53 | state.serialize_field("parameters", &self.parameters())?; 54 | state.serialize_field("inputs", &self.inputs())?; 55 | state.serialize_field("outputs", &self.outputs())?; 56 | state.end() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/protoflow-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoflow-core" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | description.workspace = true 8 | #documentation.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | license.workspace = true 13 | keywords.workspace = true 14 | categories.workspace = true 15 | publish.workspace = true 16 | 17 | [features] 18 | default = ["all", "std"] 19 | all = ["serde", "sysml", "tracing"] 20 | rand = ["dep:getrandom", "dep:rand"] # FIXME: , "rand/getrandom"] 21 | serde = ["dep:serde"] 22 | std = [ 23 | "dogma/std", 24 | "getrandom?/std", 25 | "prost/std", 26 | "prost-types/std", 27 | "rand?/std", 28 | "rand?/std_rng", 29 | "serde?/std", 30 | "sysml-model?/std", 31 | "tracing?/std", 32 | ] 33 | sysml = ["dep:sysml-model"] 34 | tokio = ["dep:tokio", "dep:async-trait"] 35 | tracing = ["dep:tracing"] 36 | unstable = [] 37 | 38 | [build-dependencies] 39 | cfg_aliases.workspace = true 40 | 41 | [dependencies] 42 | bytes = { version = "1", default-features = false } 43 | dogma = { version = "0.1", default-features = false, features = ["traits"] } 44 | getrandom = { version = "0.2", optional = true, default-features = false } 45 | parking_lot = "0.12" 46 | prost = { version = "0.13", default-features = false, features = ["derive"] } 47 | prost-types = { version = "0.13", default-features = false } 48 | rand = { version = "0.8", optional = true, default-features = false } 49 | serde = { version = "1.0", default-features = false, features = [ 50 | "derive", 51 | ], optional = true } 52 | sharded-slab = "0.1.7" 53 | stability = "0.2" 54 | sysml-model = { version = "=0.2.3", default-features = false, optional = true } 55 | tokio = { version = "1.40.0", default-features = false, optional = true } 56 | async-trait = { version = "0.1.83", optional = true } 57 | tracing = { version = "0.1", default-features = false, optional = true } 58 | 59 | [dev-dependencies] 60 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/block.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{fmt, Box}, 5 | BlockDescriptor, BlockResult, BlockRuntime, 6 | }; 7 | 8 | #[cfg(feature = "tokio")] 9 | use crate::AsyncBlock; 10 | 11 | /// A machine-readable identifier for a block in a system. 12 | /// 13 | /// Only valid within the scope of that system. 14 | pub type BlockID = usize; 15 | 16 | pub type BoxedBlock = Box; 17 | #[cfg(feature = "tokio")] 18 | pub type BoxedAsyncBlock = Box; 19 | 20 | #[derive(Debug)] 21 | pub enum BoxedBlockType { 22 | Normal(BoxedBlock), 23 | #[cfg(feature = "tokio")] 24 | Async(BoxedAsyncBlock), 25 | } 26 | 27 | /// A block is an autonomous unit of computation in a system. 28 | pub trait Block: AsBlock + BlockDescriptor + BlockHooks + Send + Sync { 29 | /// Prepares this block for execution. 30 | /// 31 | /// This is called once before the first call to `execute`. 32 | /// This is where to open ports and allocate resources. 33 | fn prepare(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 34 | Ok(()) 35 | } 36 | 37 | /// Executes this block's computation. 38 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult; 39 | } 40 | 41 | /// Hooks for `#[derive(Block)]` to tap into block execution. 42 | #[doc(hidden)] 43 | pub trait BlockHooks { 44 | fn pre_execute(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 45 | Ok(()) // implemented by protoflow_derive 46 | } 47 | 48 | fn post_execute(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 49 | Ok(()) // implemented by protoflow_derive 50 | } 51 | } 52 | 53 | pub trait AsBlock { 54 | fn as_block(&self) -> &dyn Block; 55 | } 56 | 57 | impl AsBlock for T { 58 | fn as_block(&self) -> &dyn Block { 59 | self 60 | } 61 | } 62 | 63 | impl fmt::Debug for dyn Block { 64 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 65 | f.debug_struct("Block") 66 | //.field("name", &self.name()) 67 | //.field("label", &self.label()) 68 | .field("inputs", &self.inputs()) 69 | .field("outputs", &self.outputs()) 70 | .field("parameters", &self.parameters()) 71 | .finish() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/protoflow/src/commands/execute.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::exit::ExitCode; 4 | use protoflow_blocks::{build_stdio_system, types::Encoding, StdioConfig, StdioError}; 5 | use protoflow_core::SystemExecution; 6 | use std::path::PathBuf; 7 | 8 | pub fn execute( 9 | system_uri: PathBuf, 10 | system_params: Vec<(String, String)>, 11 | stdio_encoding: Encoding, 12 | ) -> Result<(), ExitCode> { 13 | let system_uri = system_uri.to_string_lossy().to_string(); 14 | let system_config = StdioConfig { 15 | encoding: stdio_encoding, 16 | params: system_params.iter().cloned().collect(), 17 | }; 18 | let system = build_stdio_system(system_uri, system_config)?; 19 | system.execute().unwrap().join().unwrap(); // TODO: improve error handling 20 | Ok(()) 21 | } 22 | 23 | #[derive(Clone, Debug)] 24 | pub enum ExecuteError { 25 | UnknownSystem(String), 26 | UnknownParameter(String), 27 | MissingParameter(&'static str), 28 | InvalidParameter(&'static str), 29 | InvalidEncoding(String), 30 | } 31 | 32 | impl std::error::Error for ExecuteError {} 33 | 34 | impl std::fmt::Display for ExecuteError { 35 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 36 | use ExecuteError::*; 37 | match self { 38 | UnknownSystem(system) => { 39 | write!(f, "unknown system: {}", system) 40 | } 41 | UnknownParameter(parameter) => { 42 | write!(f, "unknown parameter: {}", parameter) 43 | } 44 | MissingParameter(parameter) => { 45 | write!(f, "missing parameter: {}", parameter) 46 | } 47 | InvalidParameter(parameter) => { 48 | write!(f, "invalid parameter: {}", parameter) 49 | } 50 | InvalidEncoding(encoding) => { 51 | write!(f, "invalid encoding: {}", encoding) 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl From for ExecuteError { 58 | fn from(error: StdioError) -> Self { 59 | use StdioError::*; 60 | match error { 61 | UnknownSystem(system) => Self::UnknownSystem(system), 62 | UnknownParameter(parameter) => Self::UnknownParameter(parameter), 63 | MissingParameter(parameter) => Self::MissingParameter(parameter), 64 | InvalidParameter(parameter) => Self::InvalidParameter(parameter), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoflow-blocks" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | description.workspace = true 8 | #documentation.workspace = true 9 | readme.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | license.workspace = true 13 | keywords.workspace = true 14 | categories.workspace = true 15 | publish.workspace = true 16 | 17 | [features] 18 | default = ["all", "std"] 19 | all = ["hash", "rand", "serde", "sysml", "tracing"] 20 | hash = ["hash-blake3", "hash-md5", "hash-sha1", "hash-sha2"] 21 | hash-blake3 = ["dep:blake3"] 22 | hash-md5 = ["dep:md-5"] 23 | hash-sha1 = ["dep:sha1"] 24 | hash-sha2 = ["dep:sha2"] 25 | 26 | rand = ["protoflow-core/rand"] 27 | std = [ 28 | "blake3?/std", 29 | "protoflow-core/std", 30 | "serde?/std", 31 | "sysml-model?/std", 32 | "tracing?/std", 33 | ] 34 | serde = [ 35 | "duration-str/serde", 36 | "protoflow-core/serde", 37 | "ubyte/serde", 38 | "dep:serde", 39 | "dep:serde_yml", 40 | ] 41 | sysml = ["protoflow-core/sysml", "dep:sysml-model"] 42 | tokio = ["protoflow-core/tokio", "dep:tokio"] 43 | tracing = ["protoflow-core/tracing", "dep:tracing"] 44 | unstable = ["protoflow-core/unstable", "protoflow-derive/unstable"] 45 | 46 | [build-dependencies] 47 | cfg_aliases.workspace = true 48 | 49 | [dependencies] 50 | blake3 = { version = "1.5", default-features = false, optional = true } 51 | duration-str = { version = "0.11", default-features = false } 52 | enum-iterator = "2.1" 53 | md-5 = { version = "0.10.6", default-features = false, optional = true } 54 | protoflow-core.workspace = true 55 | protoflow-derive.workspace = true 56 | tokio = { version = "1.40.0", default-features = false, optional = true } 57 | tracing = { version = "0.1", default-features = false, optional = true } 58 | serde = { version = "1.0", default-features = false, features = [ 59 | "derive", 60 | ], optional = true } 61 | serde_yml = { version = "0.0.12", optional = true } 62 | sha1 = { version = "0.10.6", default-features = false, optional = true } 63 | sha2 = { version = "0.10.8", default-features = false, optional = true } 64 | simple-mermaid = "0.1" 65 | stability = "0.2" 66 | struson = "0.5" 67 | sysml-model = { version = "=0.2.3", default-features = false, optional = true } 68 | ubyte = { version = "0.10", default-features = false } 69 | csv = "1.3.1" 70 | 71 | [dev-dependencies] 72 | bytes = "1.8.0" 73 | protoflow-derive.workspace = true 74 | tempfile = "3.13.0" 75 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![no_std] 4 | #![allow(unsafe_code)] // for `impl Sync` 5 | 6 | extern crate self as protoflow_core; 7 | 8 | #[doc(hidden)] 9 | pub mod prelude; 10 | 11 | mod block; 12 | pub use block::*; 13 | 14 | #[cfg(feature = "tokio")] 15 | mod async_block; 16 | #[cfg(feature = "tokio")] 17 | pub use async_block::*; 18 | 19 | mod block_descriptor; 20 | pub use block_descriptor::*; 21 | 22 | mod block_error; 23 | pub use block_error::*; 24 | 25 | mod block_runtime; 26 | pub use block_runtime::*; 27 | 28 | mod function_block; 29 | pub use function_block::*; 30 | 31 | mod input_port; 32 | pub use input_port::*; 33 | 34 | mod input_ports; 35 | pub use input_ports::*; 36 | 37 | mod message; 38 | pub use message::*; 39 | 40 | mod message_buffer; 41 | pub use message_buffer::*; 42 | 43 | mod message_receiver; 44 | pub use message_receiver::*; 45 | 46 | mod message_sender; 47 | pub use message_sender::*; 48 | 49 | mod output_port; 50 | pub use output_port::*; 51 | 52 | mod output_ports; 53 | pub use output_ports::*; 54 | 55 | mod parameter_descriptor; 56 | pub use parameter_descriptor::*; 57 | 58 | mod port; 59 | pub use port::*; 60 | 61 | mod port_descriptor; 62 | pub use port_descriptor::*; 63 | 64 | mod port_error; 65 | pub use port_error::*; 66 | 67 | mod port_id; 68 | pub use port_id::*; 69 | 70 | mod port_state; 71 | pub use port_state::*; 72 | 73 | mod process; 74 | pub use process::*; 75 | 76 | mod runtime; 77 | pub use runtime::*; 78 | 79 | pub mod runtimes; 80 | 81 | mod system; 82 | pub use system::*; 83 | 84 | mod transport; 85 | pub use transport::*; 86 | 87 | pub mod transports; 88 | 89 | #[allow(unused_imports)] 90 | pub(crate) mod utils { 91 | mod rw_condvar; 92 | pub use rw_condvar::*; 93 | } 94 | 95 | pub use prost_types as types; 96 | 97 | pub use prost::DecodeError; 98 | 99 | #[cfg(feature = "tracing")] 100 | #[doc(hidden)] 101 | mod tracing { 102 | pub use tracing::{debug, error, info, trace, warn}; 103 | } 104 | 105 | #[cfg(not(feature = "tracing"))] 106 | #[doc(hidden)] 107 | #[rustfmt::skip] 108 | mod tracing { 109 | #[macro_export] macro_rules! debug { ($($arg:tt)+) => (); } 110 | #[macro_export] macro_rules! error { ($($arg:tt)+) => (); } 111 | #[macro_export] macro_rules! info { ($($arg:tt)+) => (); } 112 | #[macro_export] macro_rules! trace { ($($arg:tt)+) => (); } 113 | #[macro_export] macro_rules! warn { ($($arg:tt)+) => (); } 114 | } 115 | 116 | #[allow(unused)] 117 | pub use tracing::*; 118 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/core/drop.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{StdioConfig, StdioError, StdioSystem, System}; 4 | use protoflow_core::{types::Any, Block, BlockResult, BlockRuntime, InputPort, Message}; 5 | use protoflow_derive::Block; 6 | use simple_mermaid::mermaid; 7 | 8 | /// A block that simply discards all messages it receives. 9 | /// 10 | /// # Block Diagram 11 | #[doc = mermaid!("../../../doc/core/drop.mmd")] 12 | /// 13 | /// # Sequence Diagram 14 | #[doc = mermaid!("../../../doc/core/drop.seq.mmd" framed)] 15 | /// 16 | /// # Examples 17 | /// 18 | /// ## Using the block in a system 19 | /// 20 | /// ```rust 21 | /// # use protoflow_blocks::*; 22 | /// # fn main() { 23 | /// System::build(|s| { 24 | /// let stdin = s.read_stdin(); 25 | /// let dropper = s.drop(); 26 | /// s.connect(&stdin.output, &dropper.input); 27 | /// }); 28 | /// # } 29 | /// ``` 30 | /// 31 | /// ## Running the block via the CLI 32 | /// 33 | /// ```console 34 | /// $ protoflow execute Drop 35 | /// ``` 36 | /// 37 | #[derive(Block, Clone)] 38 | pub struct Drop { 39 | /// The input message stream. 40 | #[input] 41 | pub input: InputPort, 42 | } 43 | 44 | impl Drop { 45 | pub fn new(input: InputPort) -> Self { 46 | Self { input } 47 | } 48 | } 49 | 50 | impl Drop { 51 | pub fn with_system(system: &System) -> Self { 52 | use crate::SystemBuilding; 53 | Self::new(system.input()) 54 | } 55 | } 56 | 57 | impl Block for Drop { 58 | fn execute(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 59 | while let Some(message) = self.input.recv()? { 60 | drop(message); 61 | } 62 | 63 | Ok(()) 64 | } 65 | } 66 | 67 | #[cfg(feature = "std")] 68 | impl StdioSystem for Drop { 69 | fn build_system(config: StdioConfig) -> Result { 70 | use crate::{CoreBlocks, SystemBuilding}; 71 | 72 | config.reject_any()?; 73 | 74 | Ok(System::build(|s| { 75 | let stdin = config.read_stdin(s); 76 | let dropper = s.drop(); 77 | s.connect(&stdin.output, &dropper.input); 78 | })) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::Drop; 85 | use crate::{System, SystemBuilding}; 86 | 87 | #[test] 88 | fn instantiate_block() { 89 | // Check that the block is constructible: 90 | let _ = System::build(|s| { 91 | let _ = s.block(Drop::::new(s.input())); 92 | }); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/output_ports.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | //! Output port arrays. 4 | 5 | use crate::{ 6 | prelude::{fmt, slice, AsRef, Deref, Index}, 7 | Message, MessageSender, OutputPort, PortResult, System, Transport, 8 | }; 9 | 10 | #[derive(Clone)] 11 | pub struct OutputPorts { 12 | pub(crate) array: [OutputPort; N], 13 | } 14 | 15 | impl OutputPorts { 16 | pub const LEN: usize = N; 17 | 18 | pub fn new(system: &System) -> Self { 19 | Self { 20 | array: [(); N].map(|_| OutputPort::new(system)), 21 | } 22 | } 23 | 24 | pub const fn is_empty(&self) -> bool { 25 | self.len() == 0 26 | } 27 | 28 | pub const fn len(&self) -> usize { 29 | Self::LEN 30 | } 31 | 32 | pub const fn capacity(&self) -> usize { 33 | Self::LEN as _ 34 | } 35 | 36 | #[must_use] 37 | pub fn get(&self, index: usize) -> Option<&OutputPort> { 38 | self.array.get(index) 39 | } 40 | 41 | pub fn iter(&self) -> slice::Iter> { 42 | self.into_iter() 43 | } 44 | 45 | pub const fn as_slice(&self) -> &[OutputPort] { 46 | self.array.as_slice() 47 | } 48 | } 49 | 50 | impl MessageSender for OutputPorts { 51 | fn send<'a>(&self, _message: impl Into<&'a T>) -> PortResult<()> 52 | where 53 | T: 'a, 54 | { 55 | todo!("OutputPorts::send") // TODO 56 | } 57 | } 58 | 59 | impl AsRef<[OutputPort]> for OutputPorts { 60 | fn as_ref(&self) -> &[OutputPort] { 61 | self 62 | } 63 | } 64 | 65 | impl Deref for OutputPorts { 66 | type Target = [OutputPort]; 67 | 68 | fn deref(&self) -> &Self::Target { 69 | self.array.as_slice() 70 | } 71 | } 72 | 73 | impl Index for OutputPorts { 74 | type Output = OutputPort; 75 | 76 | fn index(&self, index: usize) -> &Self::Output { 77 | &self.array[index] 78 | } 79 | } 80 | 81 | impl<'a, T: Message + 'a, const N: usize> IntoIterator for &'a OutputPorts { 82 | type Item = &'a OutputPort; 83 | type IntoIter = slice::Iter<'a, OutputPort>; 84 | 85 | fn into_iter(self) -> Self::IntoIter { 86 | self.iter() 87 | } 88 | } 89 | 90 | impl fmt::Debug for OutputPorts { 91 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 92 | f.debug_list().entries(&self.array).finish() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/input_ports.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | //! Input port arrays. 4 | 5 | use crate::{ 6 | prelude::{fmt, slice, AsRef, Deref, Index}, 7 | InputPort, Message, MessageReceiver, PortResult, System, Transport, 8 | }; 9 | 10 | #[derive(Clone)] 11 | pub struct InputPorts { 12 | pub(crate) array: [InputPort; N], 13 | } 14 | 15 | impl InputPorts { 16 | pub const LEN: usize = N; 17 | 18 | pub fn new(system: &System) -> Self { 19 | Self { 20 | array: [(); N].map(|_| InputPort::new(system)), 21 | } 22 | } 23 | 24 | pub const fn is_empty(&self) -> bool { 25 | self.len() == 0 26 | } 27 | 28 | pub const fn len(&self) -> usize { 29 | Self::LEN 30 | } 31 | 32 | pub const fn capacity(&self) -> usize { 33 | Self::LEN as _ 34 | } 35 | 36 | #[must_use] 37 | pub fn get(&self, index: usize) -> Option<&InputPort> { 38 | self.array.get(index) 39 | } 40 | 41 | pub fn iter(&self) -> slice::Iter> { 42 | self.into_iter() 43 | } 44 | 45 | pub const fn as_slice(&self) -> &[InputPort] { 46 | self.array.as_slice() 47 | } 48 | } 49 | 50 | impl MessageReceiver for InputPorts { 51 | fn recv(&self) -> PortResult> { 52 | todo!("InputPort::recv") // TODO 53 | } 54 | 55 | fn try_recv(&self) -> PortResult> { 56 | todo!("InputPort::try_recv") // TODO 57 | } 58 | } 59 | 60 | impl AsRef<[InputPort]> for InputPorts { 61 | fn as_ref(&self) -> &[InputPort] { 62 | self 63 | } 64 | } 65 | 66 | impl Deref for InputPorts { 67 | type Target = [InputPort]; 68 | 69 | fn deref(&self) -> &Self::Target { 70 | self.array.as_slice() 71 | } 72 | } 73 | 74 | impl Index for InputPorts { 75 | type Output = InputPort; 76 | 77 | fn index(&self, index: usize) -> &Self::Output { 78 | &self.array[index] 79 | } 80 | } 81 | 82 | impl<'a, T: Message + 'a, const N: usize> IntoIterator for &'a InputPorts { 83 | type Item = &'a InputPort; 84 | type IntoIter = slice::Iter<'a, InputPort>; 85 | 86 | fn into_iter(self) -> Self::IntoIter { 87 | self.iter() 88 | } 89 | } 90 | 91 | impl fmt::Debug for InputPorts { 92 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 93 | f.debug_list().entries(&self.array).finish() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/sys/write_stdout.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{prelude::Bytes, StdioConfig, StdioError, StdioSystem, System}; 6 | use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort}; 7 | use protoflow_derive::Block; 8 | use simple_mermaid::mermaid; 9 | 10 | /// A block that writes bytes to standard output (aka stdout). 11 | /// 12 | /// # Block Diagram 13 | #[doc = mermaid!("../../../doc/sys/write_stdout.mmd")] 14 | /// 15 | /// # Sequence Diagram 16 | #[doc = mermaid!("../../../doc/sys/write_stdout.seq.mmd" framed)] 17 | /// 18 | /// # Examples 19 | /// 20 | /// ## Using the block in a system 21 | /// 22 | /// ```rust 23 | /// # use protoflow_blocks::*; 24 | /// # fn main() { 25 | /// System::build(|s| { 26 | /// let stdin = s.read_stdin(); 27 | /// let stdout = s.write_stdout(); 28 | /// s.connect(&stdin.output, &stdout.input); 29 | /// }); 30 | /// # } 31 | /// ``` 32 | /// 33 | /// ## Running the block via the CLI 34 | /// 35 | /// ```console 36 | /// $ protoflow execute WriteStdout < input.txt > output.txt 37 | /// ``` 38 | /// 39 | #[derive(Block, Clone)] 40 | pub struct WriteStdout { 41 | /// The input message stream. 42 | #[input] 43 | pub input: InputPort, 44 | } 45 | 46 | impl WriteStdout { 47 | pub fn new(input: InputPort) -> Self { 48 | Self { input } 49 | } 50 | 51 | pub fn with_system(system: &System) -> Self { 52 | use crate::SystemBuilding; 53 | Self::new(system.input()) 54 | } 55 | } 56 | 57 | impl Block for WriteStdout { 58 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 59 | runtime.wait_for(&self.input)?; 60 | 61 | while let Some(message) = self.input.recv()? { 62 | let mut stdout = std::io::stdout().lock(); 63 | std::io::Write::write_all(&mut stdout, &message)?; 64 | } 65 | 66 | Ok(()) 67 | } 68 | } 69 | 70 | #[cfg(feature = "std")] 71 | impl StdioSystem for WriteStdout { 72 | fn build_system(config: StdioConfig) -> Result { 73 | use crate::SystemBuilding; 74 | 75 | config.reject_any()?; 76 | 77 | Ok(System::build(|s| { 78 | let stdin = config.read_stdin(s); 79 | let stdout = config.write_stdout(s); 80 | s.connect(&stdin.output, &stdout.input); 81 | })) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::WriteStdout; 88 | use crate::{System, SystemBuilding}; 89 | 90 | #[test] 91 | fn instantiate_block() { 92 | // Check that the block is constructible: 93 | let _ = System::build(|s| { 94 | let _ = s.block(WriteStdout::new(s.input())); 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/sys/write_stderr.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{prelude::Bytes, StdioConfig, StdioError, StdioSystem, System}; 6 | use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort}; 7 | use protoflow_derive::Block; 8 | use simple_mermaid::mermaid; 9 | 10 | /// A block that writes bytes to standard error (aka stderr). 11 | /// 12 | /// # Block Diagram 13 | #[doc = mermaid!("../../../doc/sys/write_stderr.mmd")] 14 | /// 15 | /// # Sequence Diagram 16 | #[doc = mermaid!("../../../doc/sys/write_stderr.seq.mmd" framed)] 17 | /// 18 | /// # Examples 19 | /// 20 | /// ## Using the block in a system 21 | /// 22 | /// ```rust 23 | /// # use protoflow_blocks::*; 24 | /// # fn main() { 25 | /// System::build(|s| { 26 | /// let stdin = s.read_stdin(); 27 | /// let stderr = s.write_stderr(); 28 | /// s.connect(&stdin.output, &stderr.input); 29 | /// }); 30 | /// # } 31 | /// ``` 32 | /// 33 | /// ## Running the block via the CLI 34 | /// 35 | /// ```console 36 | /// $ protoflow execute WriteStderr < input.txt 2> output.txt 37 | /// ``` 38 | /// 39 | #[derive(Block, Clone)] 40 | pub struct WriteStderr { 41 | /// The input message stream. 42 | #[input] 43 | pub input: InputPort, 44 | } 45 | 46 | impl WriteStderr { 47 | pub fn new(input: InputPort) -> Self { 48 | Self { input } 49 | } 50 | 51 | pub fn with_system(system: &System) -> Self { 52 | use crate::SystemBuilding; 53 | Self::new(system.input()) 54 | } 55 | } 56 | 57 | impl Block for WriteStderr { 58 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 59 | runtime.wait_for(&self.input)?; 60 | 61 | while let Some(message) = self.input.recv()? { 62 | let mut stderr = std::io::stderr().lock(); 63 | std::io::Write::write_all(&mut stderr, &message)?; 64 | } 65 | 66 | self.input.close()?; 67 | Ok(()) 68 | } 69 | } 70 | 71 | #[cfg(feature = "std")] 72 | impl StdioSystem for WriteStderr { 73 | fn build_system(config: StdioConfig) -> Result { 74 | use crate::SystemBuilding; 75 | 76 | config.reject_any()?; 77 | 78 | Ok(System::build(|s| { 79 | let stdin = config.read_stdin(s); 80 | let stderr = config.write_stderr(s); 81 | s.connect(&stdin.output, &stderr.input); 82 | })) 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::WriteStderr; 89 | use crate::{System, SystemBuilding}; 90 | 91 | #[test] 92 | fn instantiate_block() { 93 | // Check that the block is constructible: 94 | let _ = System::build(|s| { 95 | let _ = s.block(WriteStderr::new(s.input())); 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/protoflow/examples/async/main.rs: -------------------------------------------------------------------------------- 1 | use protoflow::{ 2 | blocks::*, derive::Block, types::Any, Block, BlockResult, BlockRuntime, InputPort, Message, 3 | OutputPort, 4 | }; 5 | 6 | use async_trait::async_trait; 7 | use std::time::Duration; 8 | 9 | #[cfg(feature = "tokio")] 10 | use protoflow::AsyncBlock; 11 | 12 | #[derive(Block, Clone)] 13 | pub struct AsyncDelay { 14 | #[input] 15 | pub input: InputPort, 16 | 17 | #[output] 18 | pub output: OutputPort, 19 | 20 | #[parameter] 21 | pub delay: Duration, 22 | } 23 | 24 | impl AsyncDelay { 25 | pub fn with_params(input: InputPort, output: OutputPort, delay: Duration) -> Self { 26 | Self { 27 | input, 28 | output, 29 | delay, 30 | } 31 | } 32 | } 33 | 34 | impl AsyncDelay { 35 | pub fn with_system(system: &System, delay: Duration) -> Self { 36 | use crate::SystemBuilding; 37 | Self::with_params(system.input(), system.output(), delay) 38 | } 39 | } 40 | 41 | #[cfg(not(feature = "tokio"))] 42 | impl Block for AsyncDelay { 43 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 44 | while let Some(s) = self.input.recv()? { 45 | runtime.sleep_for(self.delay)?; 46 | self.output.send(&s)?; 47 | } 48 | 49 | Ok(()) 50 | } 51 | } 52 | 53 | #[cfg(feature = "tokio")] 54 | #[async_trait] 55 | impl AsyncBlock for AsyncDelay { 56 | async fn execute_async(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 57 | while let Some(s) = self.input.recv()? { 58 | tokio::time::sleep(self.delay).await; 59 | self.output.send(&s)?; 60 | } 61 | 62 | Ok(()) 63 | } 64 | } 65 | 66 | fn main() -> BlockResult { 67 | let f = |s: &mut System| { 68 | let greeting = s.const_string("Hello, world!"); 69 | 70 | let line_encoder = s.encode_lines(); 71 | s.connect(&greeting.output, &line_encoder.input); 72 | 73 | let duration = Duration::from_secs(1); 74 | 75 | #[cfg(not(feature = "tokio"))] 76 | let transform = s.block(AsyncDelay::with_system(s, duration)); 77 | #[cfg(feature = "tokio")] 78 | let transform = s.block_async(AsyncDelay::with_system(s, duration)); 79 | 80 | s.connect(&line_encoder.output, &transform.input); 81 | 82 | let stdout = s.write_stdout(); 83 | s.connect(&transform.output, &stdout.input); 84 | }; 85 | 86 | #[cfg(feature = "tokio")] 87 | { 88 | let rt = tokio::runtime::Runtime::new().unwrap(); 89 | let rt_handle = rt.handle(); 90 | 91 | System::run_async(rt_handle.clone(), f) 92 | } 93 | #[cfg(not(feature = "tokio"))] 94 | System::run(f) 95 | } 96 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.4.3] - 2024-11-15 9 | ### Fixed 10 | - Feature flags 11 | 12 | ## [0.4.2] - 2024-11-15 13 | ### Added 14 | - `DecodeJson` block 15 | - `ReadFile` block 16 | - `WriteFile` block 17 | 18 | ## [0.4.1] - 2024-10-09 19 | ### Added 20 | - `EncodeJson` block 21 | - Serde support for core types 22 | ### Fixed 23 | - CLI: Fix a minor bug 24 | 25 | ## [0.4.0] - 2024-10-01 26 | 27 | ## [0.3.1] - 2024-09-25 28 | ### Added 29 | - Implement `Message` for `google.protobuf` well-known types 30 | 31 | ## [0.3.0] - 2024-09-09 32 | ### Added 33 | - `MaybeNamed` and `MaybeLabeled` for blocks & ports 34 | - `ParameterDescriptor` 35 | ### Fixed 36 | - `PortDescriptor` field visibility 37 | 38 | ## [0.2.2] - 2024-09-07 39 | ### Added 40 | - `PortDescriptor#direction` 41 | - `PortDescriptor#r#type` 42 | ### Fixed 43 | - `#[derive(FunctionBlock)]` 44 | 45 | ## [0.2.1] - 2024-08-24 46 | ### Added 47 | - Optional Serde support for block parameters 48 | 49 | ## [0.2.0] - 2024-08-21 50 | ### Added 51 | - `EncodeHex`: A block for hex encoding 52 | - `Hash`: A block for BLAKE3 hashing 53 | - Implement `BlockRuntime#wait_for()` 54 | - Add shell examples for all blocks 55 | ### Changed 56 | - `Count`: Ignore a disconnected port when sending the counter 57 | - Close all ports automatically on block exit 58 | - Send EOS prior to disconnecting the ports 59 | - Rewrite the MPSC transport for robustness 60 | ### Fixed 61 | - `Decode`: Fix line decoding to strip off trailing newlines 62 | 63 | ## [0.1.1] - 2024-08-20 64 | ### Added 65 | - Implement `System#decode_with::(Encoding::TextWithNewlineSuffix)` 66 | - A new example: [`echo_lines`](lib/protoflow/examples/echo_lines) 67 | - A new example: [`count_lines`](lib/protoflow/examples/count_lines) 68 | 69 | ## [0.1.0] - 2024-08-20 70 | 71 | [0.4.3]: https://github.com/asimov-platform/protoflow/compare/0.4.2...0.4.3 72 | [0.4.2]: https://github.com/asimov-platform/protoflow/compare/0.4.1...0.4.2 73 | [0.4.1]: https://github.com/asimov-platform/protoflow/compare/0.4.0...0.4.1 74 | [0.4.0]: https://github.com/asimov-platform/protoflow/compare/0.3.1...0.4.0 75 | [0.3.1]: https://github.com/asimov-platform/protoflow/compare/0.3.0...0.3.1 76 | [0.3.0]: https://github.com/asimov-platform/protoflow/compare/0.2.2...0.3.0 77 | [0.2.2]: https://github.com/asimov-platform/protoflow/compare/0.2.1...0.2.2 78 | [0.2.1]: https://github.com/asimov-platform/protoflow/compare/0.2.0...0.2.1 79 | [0.2.0]: https://github.com/asimov-platform/protoflow/compare/0.1.0...0.2.0 80 | [0.1.1]: https://github.com/asimov-platform/protoflow/compare/0.1.0...0.1.1 81 | [0.1.0]: https://github.com/asimov-platform/protoflow/compare/0.0.0...0.1.0 82 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/hash.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #[cfg(not(any( 4 | feature = "hash-blake3", 5 | feature = "hash-md5", 6 | feature = "hash-sha1", 7 | feature = "hash-sha2" 8 | )))] 9 | pub mod hash { 10 | pub trait HashBlocks {} 11 | pub enum HashBlockConfig {} 12 | } 13 | 14 | #[cfg(any( 15 | feature = "hash-blake3", 16 | feature = "hash-md5", 17 | feature = "hash-sha1", 18 | feature = "hash-sha2" 19 | ))] 20 | pub mod hash { 21 | use super::{ 22 | prelude::{vec, Box, Cow, Named, Vec}, 23 | types::HashAlgorithm, 24 | BlockConnections, BlockInstantiation, InputPortName, OutputPortName, System, 25 | }; 26 | use protoflow_core::Block; 27 | 28 | pub trait HashBlocks { 29 | fn hash(&mut self, algorithm: HashAlgorithm) -> Hash; 30 | 31 | #[cfg(feature = "hash-blake3")] 32 | fn hash_blake3(&mut self) -> Hash; 33 | 34 | #[cfg(feature = "hash-md5")] 35 | fn hash_md5(&mut self) -> Hash; 36 | 37 | #[cfg(feature = "hash-sha1")] 38 | fn hash_sha1(&mut self) -> Hash; 39 | 40 | #[cfg(feature = "hash-sha2")] 41 | fn hash_sha2(&mut self) -> Hash; 42 | } 43 | 44 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 45 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 46 | pub enum HashBlockTag { 47 | Hash, 48 | } 49 | 50 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 51 | #[derive(Clone, Debug)] 52 | pub enum HashBlockConfig { 53 | Hash { 54 | input: InputPortName, 55 | output: Option, 56 | hash: OutputPortName, 57 | algorithm: Option, 58 | }, 59 | } 60 | 61 | impl Named for HashBlockConfig { 62 | fn name(&self) -> Cow { 63 | use HashBlockConfig::*; 64 | Cow::Borrowed(match self { 65 | Hash { .. } => "Hash", 66 | }) 67 | } 68 | } 69 | 70 | impl BlockConnections for HashBlockConfig { 71 | fn output_connections(&self) -> Vec<(&'static str, Option)> { 72 | use HashBlockConfig::*; 73 | match self { 74 | Hash { output, hash, .. } => { 75 | vec![("output", output.clone()), ("hash", Some(hash.clone()))] 76 | } 77 | } 78 | } 79 | } 80 | 81 | impl BlockInstantiation for HashBlockConfig { 82 | fn instantiate(&self, system: &mut System) -> Box { 83 | use HashBlockConfig::*; 84 | match self { 85 | Hash { algorithm, .. } => Box::new(super::Hash::with_system(system, *algorithm)), 86 | } 87 | } 88 | } 89 | 90 | mod hash; 91 | pub use hash::*; 92 | } 93 | 94 | pub use hash::*; 95 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/core/buffer.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{prelude::VecDeque, StdioConfig, StdioError, StdioSystem, System}; 4 | use protoflow_core::{types::Any, Block, BlockResult, BlockRuntime, InputPort, Message}; 5 | use protoflow_derive::Block; 6 | use simple_mermaid::mermaid; 7 | 8 | /// A block that simply stores all messages it receives. 9 | /// 10 | /// # Block Diagram 11 | #[doc = mermaid!("../../../doc/core/buffer.mmd")] 12 | /// 13 | /// # Sequence Diagram 14 | #[doc = mermaid!("../../../doc/core/buffer.seq.mmd" framed)] 15 | /// 16 | /// # Examples 17 | /// 18 | /// ## Using the block in a system 19 | /// 20 | /// ```rust 21 | /// # use protoflow_blocks::*; 22 | /// # fn main() { 23 | /// System::build(|s| { 24 | /// let stdin = s.read_stdin(); 25 | /// let buffer = s.buffer(); 26 | /// s.connect(&stdin.output, &buffer.input); 27 | /// }); 28 | /// # } 29 | /// ``` 30 | /// 31 | /// ## Running the block via the CLI 32 | /// 33 | /// ```console 34 | /// $ protoflow execute Buffer 35 | /// ``` 36 | /// 37 | #[derive(Block, Clone)] 38 | pub struct Buffer { 39 | /// The input message stream. 40 | #[input] 41 | pub input: InputPort, 42 | 43 | /// The internal state storing the messages received. 44 | #[state] 45 | messages: VecDeque, 46 | } 47 | 48 | impl Buffer { 49 | pub fn new(input: InputPort) -> Self { 50 | Self { 51 | input, 52 | messages: VecDeque::new(), 53 | } 54 | } 55 | 56 | pub fn messages(&self) -> &VecDeque { 57 | &self.messages 58 | } 59 | } 60 | 61 | impl Buffer { 62 | pub fn with_system(system: &System) -> Self { 63 | use crate::SystemBuilding; 64 | Self::new(system.input()) 65 | } 66 | } 67 | 68 | impl Block for Buffer { 69 | fn execute(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 70 | while let Some(message) = self.input.recv()? { 71 | self.messages.push_back(message); 72 | } 73 | Ok(()) 74 | } 75 | } 76 | 77 | #[cfg(feature = "std")] 78 | impl StdioSystem for Buffer { 79 | fn build_system(config: StdioConfig) -> Result { 80 | use crate::{CoreBlocks, SystemBuilding}; 81 | 82 | config.reject_any()?; 83 | 84 | Ok(System::build(|s| { 85 | let stdin = config.read_stdin(s); 86 | let buffer = s.buffer(); 87 | s.connect(&stdin.output, &buffer.input); 88 | })) 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::Buffer; 95 | use crate::{System, SystemBuilding}; 96 | 97 | #[test] 98 | fn instantiate_block() { 99 | // Check that the block is constructible: 100 | let _ = System::build(|s| { 101 | let _ = s.block(Buffer::::new(s.input())); 102 | }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/protoflow/Cargo.toml: -------------------------------------------------------------------------------- 1 | # See: https://doc.rust-lang.org/cargo/reference/manifest.html 2 | 3 | [package] 4 | name = "protoflow" 5 | version.workspace = true 6 | authors.workspace = true 7 | edition.workspace = true 8 | rust-version.workspace = true 9 | description.workspace = true 10 | #documentation.workspace = true 11 | readme.workspace = true 12 | homepage.workspace = true 13 | repository.workspace = true 14 | license.workspace = true 15 | keywords.workspace = true 16 | categories.workspace = true 17 | publish.workspace = true 18 | 19 | [features] 20 | default = ["all", "cli", "std"] 21 | all = ["blocks", "derive", "rand", "serde", "sysml", "tracing"] 22 | beta = ["unstable"] # deprecated 23 | blocks = ["dep:protoflow-blocks"] 24 | cli = ["std", "syntax", "dep:clap", "dep:clientele"] 25 | crossbeam = ["dep:protoflow-crossbeam"] 26 | derive = ["dep:protoflow-derive"] 27 | flume = ["dep:protoflow-flume"] 28 | rand = ["protoflow-blocks?/rand", "protoflow-core/rand"] 29 | serde = ["protoflow-blocks?/serde"] 30 | std = [ 31 | "clientele?/std", 32 | "protoflow-blocks?/std", 33 | "protoflow-core/std", 34 | "protoflow-crossbeam?/std", 35 | "protoflow-derive?/std", 36 | "protoflow-flume?/std", 37 | "protoflow-syntax?/std", 38 | "protoflow-zeromq?/std", 39 | "tracing?/std", 40 | ] 41 | syntax = ["dep:protoflow-syntax"] 42 | sysml = [ 43 | "protoflow-blocks?/sysml", 44 | "protoflow-core/sysml", 45 | "protoflow-derive?/sysml", 46 | "protoflow-syntax?/sysml", 47 | ] 48 | tokio = ["protoflow-core/tokio"] 49 | tracing = ["dep:tracing", "clientele?/tracing"] # FIXME 50 | unstable = [ 51 | "protoflow-blocks?/unstable", 52 | "protoflow-core/unstable", 53 | "protoflow-derive?/unstable", 54 | "protoflow-syntax?/unstable", 55 | ] 56 | web = [] 57 | zeromq = ["dep:protoflow-zeromq"] 58 | 59 | [build-dependencies] 60 | cfg_aliases.workspace = true 61 | shadow-rs.workspace = true 62 | 63 | [dependencies] 64 | clap = { version = "4.5", default-features = false, optional = true } 65 | clientele = { version = "0.2", default-features = false, features = [ 66 | "argfile", 67 | "clap", 68 | "color", 69 | "dotenv", 70 | "parse", 71 | "unicode", 72 | "wild", 73 | ], optional = true } 74 | error-stack = { version = "0.5", default-features = false } 75 | protoflow-blocks = { version = "=0.4.3", default-features = false, optional = true } 76 | protoflow-core = { version = "=0.4.3", default-features = false } 77 | protoflow-crossbeam = { version = "=0.4.3", default-features = false, optional = true } 78 | protoflow-derive = { version = "=0.4.3", optional = true } 79 | protoflow-flume = { version = "=0.4.3", default-features = false, optional = true } 80 | protoflow-syntax = { version = "=0.4.3", default-features = false, optional = true } 81 | protoflow-zeromq = { version = "=0.4.3", default-features = false, optional = true } 82 | tracing = { version = "0.1", default-features = false, optional = true } 83 | 84 | [dev-dependencies] 85 | tokio = { version = "1.40.0", default-features = false } 86 | async-trait = { version = "0.1.83" } 87 | 88 | [[bin]] 89 | name = "protoflow" 90 | required-features = ["cli"] 91 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/io/encode_hex.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{format, Bytes, String}, 5 | IoBlocks, StdioConfig, StdioError, StdioSystem, System, 6 | }; 7 | use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort, OutputPort}; 8 | use protoflow_derive::Block; 9 | use simple_mermaid::mermaid; 10 | 11 | /// A block that encodes a byte stream into hexadecimal form. 12 | /// 13 | /// # Block Diagram 14 | #[doc = mermaid!("../../../doc/io/encode_hex.mmd")] 15 | /// 16 | /// # Sequence Diagram 17 | #[doc = mermaid!("../../../doc/io/encode_hex.seq.mmd" framed)] 18 | /// 19 | /// # Examples 20 | /// 21 | /// ## Using the block in a system 22 | /// 23 | /// ```rust 24 | /// # use protoflow_blocks::*; 25 | /// # fn main() { 26 | /// System::build(|s| { 27 | /// let stdin = s.read_stdin(); 28 | /// let hex_encoder = s.encode_hex(); 29 | /// let stdout = s.write_stdout(); 30 | /// s.connect(&stdin.output, &hex_encoder.input); 31 | /// s.connect(&hex_encoder.output, &stdout.input); 32 | /// }); 33 | /// # } 34 | /// ``` 35 | /// 36 | /// ## Running the block via the CLI 37 | /// 38 | /// ```console 39 | /// $ protoflow execute EncodeHex 40 | /// ``` 41 | /// 42 | #[derive(Block, Clone)] 43 | pub struct EncodeHex { 44 | /// The input byte stream. 45 | #[input] 46 | pub input: InputPort, 47 | 48 | /// The output text stream. 49 | #[output] 50 | pub output: OutputPort, 51 | } 52 | 53 | impl EncodeHex { 54 | pub fn new(input: InputPort, output: OutputPort) -> Self { 55 | Self { input, output } 56 | } 57 | 58 | pub fn with_system(system: &System) -> Self { 59 | use crate::SystemBuilding; 60 | Self::new(system.input(), system.output()) 61 | } 62 | } 63 | 64 | impl Block for EncodeHex { 65 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 66 | runtime.wait_for(&self.input)?; 67 | 68 | while let Some(message) = self.input.recv()? { 69 | let mut buffer = String::with_capacity(message.len() * 2); 70 | for byte in message.iter() { 71 | buffer.push_str(format!("{:02x}", byte).as_str()); // TODO: optimize 72 | } 73 | let message = Bytes::from(buffer); 74 | self.output.send(&message)?; 75 | } 76 | 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[cfg(feature = "std")] 82 | impl StdioSystem for EncodeHex { 83 | fn build_system(config: StdioConfig) -> Result { 84 | use crate::SystemBuilding; 85 | 86 | config.reject_any()?; 87 | 88 | Ok(System::build(|s| { 89 | let stdin = config.read_stdin(s); 90 | let hex_encoder = s.encode_hex(); 91 | let stdout = config.write_stdout(s); 92 | s.connect(&stdin.output, &hex_encoder.input); 93 | s.connect(&hex_encoder.output, &stdout.input); 94 | })) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::EncodeHex; 101 | use crate::{System, SystemBuilding}; 102 | 103 | #[test] 104 | fn instantiate_block() { 105 | // Check that the block is constructible: 106 | let _ = System::build(|s| { 107 | let _ = s.block(EncodeHex::new(s.input(), s.output())); 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/port_descriptor.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{type_name, Cow, MaybeLabeled, MaybeNamed, String, ToString}, 5 | InputPort, Message, OutputPort, Port, PortID, PortState, 6 | }; 7 | 8 | /// The dataflow direction of a port. 9 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 10 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 11 | #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] 12 | pub enum PortDirection { 13 | Input, 14 | Output, 15 | } 16 | 17 | /// A descriptor for a block port. 18 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 19 | #[cfg_attr(feature = "serde", derive(serde::Serialize))] 20 | pub struct PortDescriptor { 21 | /// The dataflow direction of this port. 22 | pub direction: PortDirection, 23 | 24 | /// The machine-readable name of this port, if any. 25 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 26 | pub name: Option, 27 | 28 | /// A human-readable label for this port, if any. 29 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 30 | pub label: Option, 31 | 32 | /// The data type for messages on this port. 33 | #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] 34 | pub r#type: Option, 35 | 36 | /// The unique identifier for this port. 37 | #[cfg_attr(feature = "serde", serde(skip))] 38 | pub id: PortID, 39 | 40 | /// The current state of this port. 41 | #[cfg_attr(feature = "serde", serde(skip))] 42 | pub state: PortState, 43 | } 44 | 45 | impl PortDescriptor { 46 | pub fn is_input(&self) -> bool { 47 | self.direction == PortDirection::Input 48 | } 49 | 50 | pub fn is_output(&self) -> bool { 51 | self.direction == PortDirection::Output 52 | } 53 | } 54 | 55 | impl MaybeNamed for PortDescriptor { 56 | fn name(&self) -> Option> { 57 | self.name.as_deref().map(Cow::Borrowed) 58 | } 59 | } 60 | 61 | impl MaybeLabeled for PortDescriptor { 62 | fn label(&self) -> Option> { 63 | self.label.as_deref().map(Cow::Borrowed) 64 | } 65 | } 66 | 67 | impl Port for PortDescriptor { 68 | fn id(&self) -> PortID { 69 | self.id 70 | } 71 | 72 | fn state(&self) -> PortState { 73 | self.state 74 | } 75 | } 76 | 77 | impl From<&InputPort> for PortDescriptor { 78 | fn from(port: &InputPort) -> Self { 79 | Self { 80 | direction: PortDirection::Input, 81 | name: port.name().map(|s| s.to_string()), 82 | label: port.label().map(|s| s.to_string()), 83 | r#type: Some(type_name::().to_string()), 84 | id: port.id(), 85 | state: port.state(), 86 | } 87 | } 88 | } 89 | 90 | impl From<&OutputPort> for PortDescriptor { 91 | fn from(port: &OutputPort) -> Self { 92 | Self { 93 | direction: PortDirection::Output, 94 | name: port.name().map(|s| s.to_string()), 95 | label: port.label().map(|s| s.to_string()), 96 | r#type: Some(type_name::().to_string()), 97 | id: port.id(), 98 | state: port.state(), 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![no_std] 4 | #![deny(unsafe_code)] 5 | 6 | #[doc(hidden)] 7 | pub use protoflow_core::prelude; 8 | 9 | mod block_config; 10 | pub use block_config::*; 11 | 12 | mod block_connections; 13 | pub use block_connections::*; 14 | 15 | mod block_instantiation; 16 | pub use block_instantiation::*; 17 | 18 | mod block_tag; 19 | pub use block_tag::*; 20 | 21 | #[cfg(feature = "std")] 22 | mod stdio; 23 | #[cfg(feature = "std")] 24 | pub use stdio::*; 25 | 26 | mod system; 27 | pub use system::*; 28 | 29 | pub mod types; 30 | pub use types::*; 31 | 32 | pub use protoflow_core::{SystemBuilding, SystemExecution}; 33 | 34 | include!("blocks/core.rs"); // CoreBlocks 35 | include!("blocks/flow.rs"); // FlowBlocks 36 | include!("blocks/hash.rs"); // HashBlocks 37 | include!("blocks/io.rs"); // IoBlocks 38 | include!("blocks/math.rs"); // MathBlocks 39 | include!("blocks/sys.rs"); // SysBlocks 40 | include!("blocks/text.rs"); // TextBlocks 41 | 42 | pub trait AllBlocks: 43 | CoreBlocks + FlowBlocks + HashBlocks + IoBlocks + MathBlocks + SysBlocks + TextBlocks 44 | { 45 | } 46 | 47 | #[cfg(feature = "std")] 48 | #[doc(hidden)] 49 | pub fn build_stdio_system( 50 | system_name: prelude::String, 51 | config: StdioConfig, 52 | ) -> Result { 53 | use prelude::String; 54 | Ok(match system_name.as_ref() { 55 | // CoreBlocks 56 | "Buffer" => Buffer::::build_system(config)?, 57 | "Const" => Const::::build_system(config)?, 58 | "Count" => Count::::build_system(config)?, 59 | "Delay" => Delay::::build_system(config)?, 60 | "Drop" => Drop::::build_system(config)?, 61 | "Random" => Random::::build_system(config)?, 62 | // FlowBlocks 63 | // HashBlocks 64 | #[cfg(any( 65 | feature = "hash-blake3", 66 | feature = "hash-md5", 67 | feature = "hash-sha1", 68 | feature = "hash-sha2" 69 | ))] 70 | "Hash" => Hash::build_system(config)?, 71 | // IoBlocks 72 | "Decode" => Decode::build_system(config)?, 73 | "DecodeHex" => DecodeHex::build_system(config)?, 74 | "DecodeJSON" => DecodeJson::build_system(config)?, 75 | "Encode" => Encode::build_system(config)?, 76 | "EncodeHex" => EncodeHex::build_system(config)?, 77 | "EncodeJSON" => EncodeJson::build_system(config)?, 78 | // MathBlocks 79 | // SysBlocks 80 | "ReadDir" => ReadDir::build_system(config)?, 81 | "ReadEnv" => ReadEnv::::build_system(config)?, 82 | "ReadFile" => ReadFile::build_system(config)?, 83 | #[cfg(feature = "serde")] 84 | "ReadSocket" => ReadSocket::build_system(config)?, 85 | "ReadStdin" => ReadStdin::build_system(config)?, 86 | "WriteFile" => WriteFile::build_system(config)?, 87 | #[cfg(feature = "serde")] 88 | "WriteSocket" => WriteSocket::build_system(config)?, 89 | "WriteStderr" => WriteStderr::build_system(config)?, 90 | "WriteStdout" => WriteStdout::build_system(config)?, 91 | // TextBlocks 92 | "ConcatStrings" => ConcatStrings::build_system(config)?, 93 | "DecodeCSV" => DecodeCsv::build_system(config)?, 94 | "EncodeCSV" => EncodeCsv::build_system(config)?, 95 | "SplitString" => SplitString::build_system(config)?, 96 | _ => return Err(StdioError::UnknownSystem(system_name))?, 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/core/random.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{prelude::vec, StdioConfig, StdioError, StdioSystem, System}; 4 | use protoflow_core::{Block, BlockResult, BlockRuntime, Message, OutputPort}; 5 | use protoflow_derive::Block; 6 | use simple_mermaid::mermaid; 7 | 8 | /// A block for generating and sending a random value. 9 | /// 10 | /// # Block Diagram 11 | #[doc = mermaid!("../../../doc/core/random.mmd")] 12 | /// 13 | /// # Sequence Diagram 14 | #[doc = mermaid!("../../../doc/core/random.seq.mmd" framed)] 15 | /// 16 | /// # Examples 17 | /// 18 | /// ## Using the block in a system 19 | /// 20 | /// ```rust 21 | /// # use protoflow_blocks::*; 22 | /// # fn main() { 23 | /// System::build(|s| { 24 | /// let random_generator = s.random::(); 25 | /// let number_encoder = s.encode_lines(); 26 | /// let stdout = s.write_stdout(); 27 | /// s.connect(&random_generator.output, &number_encoder.input); 28 | /// s.connect(&number_encoder.output, &stdout.input); 29 | /// }); 30 | /// # } 31 | /// ``` 32 | /// 33 | /// ## Running the block via the CLI 34 | /// 35 | /// ```console 36 | /// $ protoflow execute Random 37 | /// ``` 38 | /// 39 | /// ```console 40 | /// $ protoflow execute Random seed=42 41 | /// ``` 42 | /// 43 | #[derive(Block, Clone)] 44 | pub struct Random { 45 | /// The port to send the value on. 46 | #[output] 47 | pub output: OutputPort, 48 | 49 | /// A parameter for the random seed to use. 50 | #[parameter] 51 | pub seed: Option, 52 | } 53 | 54 | impl Random { 55 | pub fn new(output: OutputPort) -> Self { 56 | Self::with_params(output, None) 57 | } 58 | 59 | pub fn with_params(output: OutputPort, seed: Option) -> Self { 60 | Self { output, seed } 61 | } 62 | } 63 | 64 | impl Random { 65 | pub fn with_system(system: &System, seed: Option) -> Self { 66 | use crate::SystemBuilding; 67 | Self::with_params(system.output(), seed) 68 | } 69 | } 70 | 71 | impl Block for Random { 72 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 73 | runtime.wait_for(&self.output)?; 74 | 75 | self.output.send(&T::default())?; // TODO 76 | 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[cfg(feature = "std")] 82 | impl StdioSystem for Random { 83 | fn build_system(config: StdioConfig) -> Result { 84 | use crate::{CoreBlocks, IoBlocks, SystemBuilding}; 85 | 86 | config.allow_only(vec!["seed"])?; 87 | let seed = config.get_opt::("seed")?; 88 | 89 | Ok(System::build(|s| { 90 | let random_generator = s.random_seeded::(seed); 91 | let number_encoder = s.encode_with::(config.encoding); 92 | let stdout = config.write_stdout(s); 93 | s.connect(&random_generator.output, &number_encoder.input); 94 | s.connect(&number_encoder.output, &stdout.input); 95 | })) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::Random; 102 | use crate::{System, SystemBuilding}; 103 | 104 | #[test] 105 | fn instantiate_block() { 106 | // Check that the block is constructible: 107 | let _ = System::build(|s| { 108 | let _ = s.block(Random::::new(s.output())); 109 | }); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/stdio.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{types::Encoding, ReadStdin, SysBlocks, System, WriteStderr, WriteStdout}; 6 | use protoflow_core::prelude::{BTreeMap, FromStr, String, Vec}; 7 | 8 | pub trait StdioSystem { 9 | fn build_system(config: StdioConfig) -> Result; 10 | } 11 | 12 | #[derive(Debug, Default, Clone)] 13 | pub struct StdioConfig { 14 | pub encoding: Encoding, 15 | pub params: BTreeMap, 16 | } 17 | 18 | impl StdioConfig { 19 | pub fn reject_any(&self) -> Result<(), StdioError> { 20 | if !self.params.is_empty() { 21 | return Err(StdioError::UnknownParameter( 22 | self.params.keys().next().unwrap().clone(), 23 | )); 24 | } 25 | Ok(()) 26 | } 27 | 28 | pub fn allow_only(&self, keys: Vec<&'static str>) -> Result<(), StdioError> { 29 | for key in self.params.keys() { 30 | if !keys.contains(&key.as_str()) { 31 | return Err(StdioError::UnknownParameter(key.clone())); 32 | } 33 | } 34 | Ok(()) 35 | } 36 | 37 | pub fn get(&self, key: &'static str) -> Result { 38 | self.get_string(key)? 39 | .parse::() 40 | .map_err(|_| StdioError::InvalidParameter(key)) 41 | } 42 | 43 | pub fn get_opt(&self, key: &'static str) -> Result, StdioError> { 44 | match self.params.get(key) { 45 | Some(value) => value 46 | .parse::() 47 | .map_err(|_| StdioError::InvalidParameter(key)) 48 | .map(Some), 49 | None => Ok(None), 50 | } 51 | } 52 | 53 | pub fn get_string(&self, key: &'static str) -> Result { 54 | let Some(value) = self.params.get(key).map(String::clone) else { 55 | return Err(StdioError::MissingParameter(key))?; 56 | }; 57 | Ok(value) 58 | } 59 | 60 | pub fn read_stdin(&self, system: &mut System) -> ReadStdin { 61 | system.read_stdin() // TODO: support override 62 | } 63 | 64 | pub fn write_stdout(&self, system: &mut System) -> WriteStdout { 65 | system.write_stdout() // TODO: support override 66 | } 67 | 68 | pub fn write_stderr(&self, system: &mut System) -> WriteStderr { 69 | system.write_stderr() // TODO: support override 70 | } 71 | } 72 | 73 | #[derive(Clone, Debug)] 74 | pub enum StdioError { 75 | UnknownSystem(String), 76 | UnknownParameter(String), 77 | MissingParameter(&'static str), 78 | InvalidParameter(&'static str), 79 | } 80 | 81 | impl std::error::Error for StdioError {} 82 | 83 | impl std::fmt::Display for StdioError { 84 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 85 | use StdioError::*; 86 | match self { 87 | UnknownSystem(system) => { 88 | write!(f, "unknown system: {}", system) 89 | } 90 | UnknownParameter(parameter) => { 91 | write!(f, "unknown parameter: {}", parameter) 92 | } 93 | MissingParameter(parameter) => { 94 | write!(f, "missing parameter: {}", parameter) 95 | } 96 | InvalidParameter(parameter) => { 97 | write!(f, "invalid parameter: {}", parameter) 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/protoflow-syntax/src/codegen.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{fmt, String, Vec}, 7 | AnalysisError, 8 | }; 9 | use error_stack::Report; 10 | use quote::{format_ident, quote, ToTokens}; 11 | use sysml_parser::{ParsedBlock, ParsedImport, ParsedMember, ParsedModel}; 12 | 13 | #[derive(Debug, Default)] 14 | pub struct Code(proc_macro2::TokenStream); 15 | 16 | impl Code { 17 | pub fn unparse(&self) -> String { 18 | let file = syn::parse2::(self.0.clone()).unwrap(); 19 | prettyplease::unparse(&file) 20 | } 21 | } 22 | 23 | impl fmt::Display for Code { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | self.0.fmt(f) 26 | } 27 | } 28 | 29 | impl ToTokens for Code { 30 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 31 | self.0.to_tokens(tokens); 32 | } 33 | } 34 | 35 | impl TryFrom<&ParsedModel> for Code { 36 | type Error = Report; 37 | 38 | fn try_from(model: &ParsedModel) -> Result { 39 | let members = model 40 | .members() 41 | .into_iter() 42 | .map(|member| Code::try_from(member)) 43 | .collect::, _>>()?; 44 | Ok(Code(quote! { 45 | fn main() { 46 | #(#members)* 47 | } 48 | })) 49 | } 50 | } 51 | 52 | impl TryFrom<&ParsedMember> for Code { 53 | type Error = Report; 54 | 55 | fn try_from(member: &ParsedMember) -> Result { 56 | use ParsedMember::*; 57 | match member { 58 | Import(import) => Code::try_from(import), 59 | Package(package) => { 60 | let members = package 61 | .members() 62 | .into_iter() 63 | .map(|member| Code::try_from(member)) 64 | .collect::, _>>()?; 65 | Ok(Code(quote! { 66 | #(#members)* 67 | })) 68 | } 69 | BlockUsage(usage) => Code::try_from(usage), 70 | AttributeUsage(_usage) => Ok(Code::default()), // TODO 71 | PortUsage(_usage) => Ok(Code::default()), // TODO 72 | } 73 | } 74 | } 75 | 76 | impl TryFrom<&ParsedImport> for Code { 77 | type Error = Report; 78 | 79 | fn try_from(import: &ParsedImport) -> Result { 80 | Ok(Self(match import.imported_name.to_tuple3() { 81 | (Some("Protoflow"), Some("*") | Some("**"), None) => { 82 | quote! { 83 | use protoflow::*; 84 | } 85 | } 86 | (Some("Protoflow"), Some(unqualified_name), None) => { 87 | let block_name = format_ident!("{}", unqualified_name); 88 | quote! { 89 | use protoflow::blocks::#block_name; 90 | } 91 | } 92 | _ => { 93 | return Err(Report::new(AnalysisError::InvalidImport( 94 | import.imported_name.clone(), 95 | ))); 96 | } 97 | })) 98 | } 99 | } 100 | 101 | impl TryFrom<&ParsedBlock> for Code { 102 | type Error = Report; 103 | 104 | fn try_from(usage: &ParsedBlock) -> Result { 105 | let name = format_ident!( 106 | "{}", 107 | usage 108 | .name 109 | .as_ref() 110 | .map(|s| s.as_str()) 111 | .unwrap_or_else(|| "block") 112 | ); 113 | Ok(Self(quote! { 114 | let #name = s.block(); 115 | })) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/sys/read_env.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{vec, FromStr, String}, 7 | StdioConfig, StdioError, StdioSystem, System, 8 | }; 9 | use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort, Message, OutputPort}; 10 | use protoflow_derive::Block; 11 | use simple_mermaid::mermaid; 12 | 13 | /// A block that reads the value of an environment variable. 14 | /// 15 | /// # Block Diagram 16 | #[doc = mermaid!("../../../doc/sys/read_env.mmd")] 17 | /// 18 | /// # Sequence Diagram 19 | #[doc = mermaid!("../../../doc/sys/read_env.seq.mmd" framed)] 20 | /// 21 | /// # Examples 22 | /// 23 | /// ## Using the block in a system 24 | /// 25 | /// ```rust 26 | /// # use protoflow_blocks::*; 27 | /// # fn main() { 28 | /// System::build(|s| { 29 | /// let name_param = s.const_string("TERM"); 30 | /// let env_reader = s.read_env(); 31 | /// let line_encoder = s.encode_lines(); 32 | /// let stdout = s.write_stdout(); 33 | /// s.connect(&name_param.output, &env_reader.name); 34 | /// s.connect(&env_reader.output, &line_encoder.input); 35 | /// s.connect(&line_encoder.output, &stdout.input); 36 | /// }); 37 | /// # } 38 | /// ``` 39 | /// 40 | /// ## Running the block via the CLI 41 | /// 42 | /// ```console 43 | /// $ protoflow execute ReadEnv name=TERM 44 | /// ``` 45 | /// 46 | #[derive(Block, Clone)] 47 | pub struct ReadEnv { 48 | /// The name of the environment variable to read. 49 | #[input] 50 | pub name: InputPort, 51 | 52 | /// The output message stream. 53 | #[output] 54 | pub output: OutputPort, 55 | } 56 | 57 | impl ReadEnv { 58 | pub fn new(name: InputPort, output: OutputPort) -> Self { 59 | Self { name, output } 60 | } 61 | } 62 | 63 | impl ReadEnv { 64 | pub fn with_system(system: &System) -> Self { 65 | use crate::SystemBuilding; 66 | Self::new(system.input(), system.output()) 67 | } 68 | } 69 | 70 | impl Block for ReadEnv { 71 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 72 | runtime.wait_for(&self.name)?; 73 | let name = self.name.recv()?.unwrap(); 74 | //self.name.close()?; // FIXME 75 | 76 | let value = std::env::var(&name).unwrap_or_default(); 77 | let value = T::from_str(&value).unwrap_or_default(); 78 | self.output.send(&value)?; 79 | 80 | self.output.close()?; 81 | Ok(()) 82 | } 83 | } 84 | 85 | #[cfg(feature = "std")] 86 | impl StdioSystem for ReadEnv { 87 | fn build_system(config: StdioConfig) -> Result { 88 | use crate::{CoreBlocks, IoBlocks, SysBlocks, SystemBuilding}; 89 | 90 | config.allow_only(vec!["name"])?; 91 | let name = config.get_string("name")?; 92 | 93 | Ok(System::build(|s| { 94 | let name_param = s.const_string(name); 95 | let env_reader = s.read_env(); 96 | let line_encoder = s.encode_with(config.encoding); 97 | let stdout = config.write_stdout(s); 98 | s.connect(&name_param.output, &env_reader.name); 99 | s.connect(&env_reader.output, &line_encoder.input); 100 | s.connect(&line_encoder.output, &stdout.input); 101 | })) 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::ReadEnv; 108 | use crate::{System, SystemBuilding}; 109 | 110 | #[test] 111 | fn instantiate_block() { 112 | // Check that the block is constructible: 113 | let _ = System::build(|s| { 114 | let _ = s.block(ReadEnv::::new(s.input(), s.output())); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/sys/read_dir.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{vec, String, ToString}, 7 | StdioConfig, StdioError, StdioSystem, System, 8 | }; 9 | use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort, OutputPort}; 10 | use protoflow_derive::Block; 11 | use simple_mermaid::mermaid; 12 | 13 | /// A block that reads file names from a file system directory. 14 | /// 15 | /// # Block Diagram 16 | #[doc = mermaid!("../../../doc/sys/read_dir.mmd")] 17 | /// 18 | /// # Sequence Diagram 19 | #[doc = mermaid!("../../../doc/sys/read_dir.seq.mmd" framed)] 20 | /// 21 | /// # Examples 22 | /// 23 | /// ## Using the block in a system 24 | /// 25 | /// ```rust 26 | /// # use protoflow_blocks::*; 27 | /// # fn main() { 28 | /// System::build(|s| { 29 | /// let path_param = s.const_string("/tmp"); 30 | /// let dir_reader = s.read_dir(); 31 | /// let line_encoder = s.encode_lines(); 32 | /// let stdout = s.write_stdout(); 33 | /// s.connect(&path_param.output, &dir_reader.path); 34 | /// s.connect(&dir_reader.output, &line_encoder.input); 35 | /// s.connect(&line_encoder.output, &stdout.input); 36 | /// }); 37 | /// # } 38 | /// ``` 39 | /// 40 | /// ## Running the block via the CLI 41 | /// 42 | /// ```console 43 | /// $ protoflow execute ReadDir path=/tmp 44 | /// ``` 45 | /// 46 | #[derive(Block, Clone)] 47 | pub struct ReadDir { 48 | /// The path to the directory to read. 49 | #[input] 50 | pub path: InputPort, 51 | 52 | /// The output message stream. 53 | #[output] 54 | pub output: OutputPort, 55 | } 56 | 57 | impl ReadDir { 58 | pub fn new(path: InputPort, output: OutputPort) -> Self { 59 | Self { path, output } 60 | } 61 | 62 | pub fn with_system(system: &System) -> Self { 63 | use crate::SystemBuilding; 64 | Self::new(system.input(), system.output()) 65 | } 66 | } 67 | 68 | impl Block for ReadDir { 69 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 70 | runtime.wait_for(&self.path)?; 71 | let dir_path = self.path.recv()?.unwrap(); 72 | //self.path.close()?; // FIXME 73 | 74 | let dir = std::fs::read_dir(dir_path)?; 75 | for dir_entry in dir { 76 | let file_path = dir_entry?.path(); 77 | //let file_path = file_path.strip_prefix("./").unwrap(); // TODO: parameter 78 | let file_path = file_path.to_string_lossy().to_string(); 79 | self.output.send(&file_path)?; 80 | } 81 | 82 | self.output.close()?; 83 | Ok(()) 84 | } 85 | } 86 | 87 | #[cfg(feature = "std")] 88 | impl StdioSystem for ReadDir { 89 | fn build_system(config: StdioConfig) -> Result { 90 | use crate::{CoreBlocks, IoBlocks, SysBlocks, SystemBuilding}; 91 | 92 | config.allow_only(vec!["path"])?; 93 | let path = config.get_string("path")?; 94 | 95 | Ok(System::build(|s| { 96 | let path_param = s.const_string(path); 97 | let dir_reader = s.read_dir(); 98 | let line_encoder = s.encode_with(config.encoding); 99 | let stdout = config.write_stdout(s); 100 | s.connect(&path_param.output, &dir_reader.path); 101 | s.connect(&dir_reader.output, &line_encoder.input); 102 | s.connect(&line_encoder.output, &stdout.input); 103 | })) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::ReadDir; 110 | use crate::{System, SystemBuilding}; 111 | 112 | #[test] 113 | fn instantiate_block() { 114 | // Check that the block is constructible: 115 | let _ = System::build(|s| { 116 | let _ = s.block(ReadDir::new(s.input(), s.output())); 117 | }); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/core/const.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{vec, String}, 5 | StdioConfig, StdioError, StdioSystem, System, 6 | }; 7 | use protoflow_core::{Block, BlockResult, BlockRuntime, Message, OutputPort}; 8 | use protoflow_derive::Block; 9 | use simple_mermaid::mermaid; 10 | 11 | /// A block for sending a constant value. 12 | /// 13 | /// This block sends a constant value on its output port. 14 | /// It can also be used to send a constant value to multiple blocks. 15 | /// 16 | /// The value to send is specified as a parameter, and can be of any 17 | /// type that implements the [`Message`] trait. 18 | /// 19 | /// The block waits for the output port to be connected before sending 20 | /// the value, and closes the port after the value is sent. 21 | /// 22 | /// The block does not have any input ports nor state. 23 | /// 24 | /// # Block Diagram 25 | #[doc = mermaid!("../../../doc/core/const.mmd")] 26 | /// 27 | /// # Sequence Diagram 28 | #[doc = mermaid!("../../../doc/core/const.seq.mmd" framed)] 29 | /// 30 | /// # Examples 31 | /// 32 | /// ## Using the block in a system 33 | /// 34 | /// ```rust 35 | /// # use protoflow_blocks::*; 36 | /// # fn main() { 37 | /// System::build(|s| { 38 | /// let const_value = s.const_string("Hello, world!"); 39 | /// let line_encoder = s.encode_lines(); 40 | /// let stdout = s.write_stdout(); 41 | /// s.connect(&const_value.output, &line_encoder.input); 42 | /// s.connect(&line_encoder.output, &stdout.input); 43 | /// }); 44 | /// # } 45 | /// ``` 46 | /// 47 | /// ## Running the block via the CLI 48 | /// 49 | /// ```console 50 | /// $ protoflow execute Const value=Hello 51 | /// ``` 52 | /// 53 | #[derive(Block, Clone)] 54 | pub struct Const { 55 | /// The port to send the value on. 56 | #[output] 57 | pub output: OutputPort, 58 | 59 | /// A parameter for the value to send. 60 | #[parameter] 61 | pub value: T, 62 | } 63 | 64 | impl Const { 65 | pub fn new(output: OutputPort) -> Self { 66 | Self::with_params(output, T::default()) 67 | } 68 | } 69 | 70 | impl Const { 71 | pub fn with_params(output: OutputPort, value: T) -> Self { 72 | Self { output, value } 73 | } 74 | } 75 | 76 | impl Const { 77 | pub fn with_system(system: &System, value: T) -> Self { 78 | use crate::SystemBuilding; 79 | Self::with_params(system.output(), value) 80 | } 81 | } 82 | 83 | impl Block for Const { 84 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 85 | runtime.wait_for(&self.output)?; 86 | 87 | self.output.send(&self.value)?; 88 | 89 | Ok(()) 90 | } 91 | } 92 | 93 | #[cfg(feature = "std")] 94 | impl StdioSystem for Const { 95 | fn build_system(config: StdioConfig) -> Result { 96 | use crate::{CoreBlocks, IoBlocks, SystemBuilding}; 97 | 98 | config.allow_only(vec!["value"])?; 99 | let value = config.get_string("value")?; 100 | 101 | Ok(System::build(|s| { 102 | let const_value = s.const_string(value); // FIXME 103 | let line_encoder = s.encode_with(config.encoding); 104 | let stdout = config.write_stdout(s); 105 | s.connect(&const_value.output, &line_encoder.input); 106 | s.connect(&line_encoder.output, &stdout.input); 107 | })) 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | use super::Const; 114 | use crate::{System, SystemBuilding}; 115 | 116 | #[test] 117 | fn instantiate_block() { 118 | // Check that the block is constructible: 119 | let _ = System::build(|s| { 120 | let _ = s.block(Const::::with_params(s.output(), 0x00BAB10C)); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/protoflow/src/exit.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use clientele::SysexitsError; 4 | 5 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 6 | pub struct ExitCode(pub SysexitsError); 7 | 8 | impl core::fmt::Display for ExitCode { 9 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 10 | write!(f, "{}", self.0) 11 | } 12 | } 13 | 14 | impl std::process::Termination for ExitCode { 15 | fn report(self) -> std::process::ExitCode { 16 | self.0.report() 17 | } 18 | } 19 | 20 | impl std::error::Error for ExitCode {} 21 | 22 | #[cfg(not(feature = "std"))] 23 | impl error_stack::Context for ExitCode {} 24 | 25 | impl From> for ExitCode { 26 | fn from(error: std::boxed::Box) -> Self { 27 | std::eprintln!("{}: {:?}", "protoflow", error); 28 | Self(SysexitsError::from(error)) 29 | } 30 | } 31 | 32 | impl From for ExitCode { 33 | fn from(error: std::io::Error) -> Self { 34 | std::eprintln!("{}: {:?}", "protoflow", error); 35 | Self(SysexitsError::from(error)) 36 | } 37 | } 38 | 39 | impl From> for ExitCode { 40 | fn from(error: error_stack::Report) -> Self { 41 | use protoflow_syntax::AnalysisError::*; 42 | std::eprintln!("{}: {:?}", "protoflow", error); // TODO: pretty print it 43 | match error.current_context() { 44 | ParseFailure => Self(SysexitsError::EX_NOINPUT), 45 | InvalidImport(_) => Self(SysexitsError::EX_DATAERR), 46 | UnknownName(_) => Self(SysexitsError::EX_DATAERR), 47 | Other(_) => Self(SysexitsError::EX_SOFTWARE), 48 | } 49 | } 50 | } 51 | 52 | impl From for ExitCode { 53 | fn from(error: protoflow_blocks::StdioError) -> Self { 54 | use protoflow_blocks::StdioError::*; 55 | std::eprintln!("{}: {}", "protoflow", error); 56 | match error { 57 | UnknownSystem(_) => Self(SysexitsError::EX_UNAVAILABLE), 58 | UnknownParameter(_) | MissingParameter(_) | InvalidParameter(_) => { 59 | Self(SysexitsError::EX_USAGE) 60 | } 61 | } 62 | } 63 | } 64 | 65 | impl From for ExitCode { 66 | fn from(error: protoflow_syntax::ParseError) -> Self { 67 | std::eprintln!("{}: {:?}", "protoflow", error); 68 | Self(SysexitsError::EX_NOINPUT) 69 | } 70 | } 71 | 72 | #[cfg(feature = "beta")] 73 | impl From for ExitCode { 74 | fn from(error: crate::commands::check::CheckError) -> Self { 75 | use crate::commands::check::CheckError::*; 76 | std::eprintln!("{}: {:?}", "protoflow", error); 77 | match error { 78 | _ => Self(SysexitsError::EX_SOFTWARE), // TODO 79 | } 80 | } 81 | } 82 | 83 | impl From for ExitCode { 84 | fn from(error: crate::commands::execute::ExecuteError) -> Self { 85 | use crate::commands::execute::ExecuteError::*; 86 | std::eprintln!("{}: {}", "protoflow", error); 87 | match error { 88 | UnknownSystem(_) => Self(SysexitsError::EX_UNAVAILABLE), 89 | UnknownParameter(_) | MissingParameter(_) | InvalidParameter(_) => { 90 | Self(SysexitsError::EX_USAGE) 91 | } 92 | InvalidEncoding(_) => Self(SysexitsError::EX_USAGE), 93 | } 94 | } 95 | } 96 | 97 | #[cfg(feature = "beta")] 98 | impl From for ExitCode { 99 | fn from(error: crate::commands::generate::GenerateError) -> Self { 100 | use crate::commands::generate::GenerateError::*; 101 | std::eprintln!("{}: {:?}", "protoflow", error); 102 | match error { 103 | _ => Self(SysexitsError::EX_SOFTWARE), // TODO 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/protoflow/src/main.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | #![deny(unsafe_code)] 4 | #![allow(unused)] 5 | 6 | mod commands { 7 | #[cfg(feature = "beta")] 8 | pub mod check; 9 | #[cfg(feature = "beta")] 10 | pub mod config; 11 | pub mod execute; 12 | #[cfg(feature = "beta")] 13 | pub mod generate; 14 | } 15 | use commands::*; 16 | 17 | mod exit; 18 | 19 | use crate::exit::ExitCode; 20 | use clientele::{ 21 | crates::clap::{Args, Parser, Subcommand}, 22 | StandardOptions, 23 | }; 24 | use protoflow_blocks::types::Encoding; 25 | use std::{error::Error, path::PathBuf, str::FromStr}; 26 | 27 | /// Protoflow Command-Line Interface (CLI) 28 | #[derive(Debug, Parser)] 29 | #[command(name = "Protoflow")] 30 | #[command(arg_required_else_help = true)] 31 | struct Options { 32 | #[clap(flatten)] 33 | flags: StandardOptions, 34 | 35 | #[command(subcommand)] 36 | command: Option, 37 | } 38 | 39 | #[derive(Debug, Subcommand)] 40 | enum Command { 41 | /// Show the current configuration 42 | #[cfg(feature = "beta")] 43 | Config {}, 44 | 45 | /// Check the syntax of a Protoflow system 46 | #[cfg(feature = "beta")] 47 | Check { 48 | /// Pathnames of Protoflow files to check 49 | #[clap(default_value = "/dev/stdin")] 50 | paths: Vec, 51 | }, 52 | 53 | /// Execute a Protoflow system or block 54 | Execute { 55 | /// Pathname of the Protoflow system or block 56 | block: PathBuf, 57 | 58 | /// Specify the message encoding to use on stdin/stdout 59 | #[clap(short = 'e', long, value_parser = parse_encoding, default_value = "text")] 60 | encoding: Encoding, 61 | 62 | /// Specify block parameters in key=value format 63 | #[clap(value_parser = parse_kv_param::)] 64 | params: Vec<(String, String)>, 65 | }, 66 | 67 | /// Generate code from a Protoflow system 68 | #[cfg(feature = "beta")] 69 | Generate { 70 | /// Pathname of the Protoflow file 71 | path: PathBuf, 72 | }, 73 | } 74 | 75 | pub fn main() -> Result<(), ExitCode> { 76 | // Load environment variables from `.env`: 77 | clientele::dotenv().ok(); 78 | 79 | // Expand wildcards and @argfiles: 80 | let args = clientele::args_os()?; 81 | 82 | // Parse command-line options: 83 | let options = Options::parse_from(args); 84 | 85 | if options.flags.version { 86 | println!("protoflow {}", env!("CARGO_PKG_VERSION")); 87 | return Ok(()); 88 | } 89 | 90 | if options.flags.license { 91 | println!("This is free and unencumbered software released into the public domain."); 92 | return Ok(()); 93 | } 94 | 95 | // Configure verbose/debug output: 96 | if options.flags.verbose > 0 || options.flags.debug { 97 | // TODO: configure tracing 98 | } 99 | 100 | match options.command.unwrap() { 101 | #[cfg(feature = "beta")] 102 | Command::Config {} => config::config(), 103 | #[cfg(feature = "beta")] 104 | Command::Check { paths } => check::check(paths), 105 | Command::Execute { 106 | block, 107 | encoding, 108 | params, 109 | } => execute::execute(block, params, encoding), 110 | #[cfg(feature = "beta")] 111 | Command::Generate { path } => generate::generate(path), 112 | } 113 | } 114 | 115 | fn parse_encoding(input: &str) -> Result { 116 | input 117 | .parse() 118 | .map_err(|e: String| execute::ExecuteError::InvalidEncoding(e)) 119 | } 120 | 121 | fn parse_kv_param(input: &str) -> Result<(K, V), Box> 122 | where 123 | K: FromStr, 124 | K::Err: Error + Send + Sync + 'static, 125 | V: FromStr, 126 | V::Err: Error + Send + Sync + 'static, 127 | { 128 | let split_pos = input 129 | .find('=') 130 | .ok_or_else(|| format!("invalid key=value parameter"))?; 131 | Ok((input[..split_pos].parse()?, input[split_pos + 1..].parse()?)) 132 | } 133 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/text/decode_csv.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{Bytes, ToString}, 7 | StdioConfig, StdioError, StdioSystem, System, 8 | }; 9 | use protoflow_core::{ 10 | types::{Value, value::Kind::*, ListValue as LV}, 11 | Block, BlockError, BlockResult, BlockRuntime, InputPort, OutputPort, 12 | }; 13 | use protoflow_derive::Block; 14 | use simple_mermaid::mermaid; 15 | use csv::ReaderBuilder; 16 | use std::io::Cursor; 17 | /// A block that decodes CSV files from a byte stream into a header and rows represented as `prost_types::Value`. 18 | /// 19 | /// # Block Diagram 20 | #[doc = mermaid!("../../../doc/text/decode_csv.mmd")] 21 | /// 22 | /// # Sequence Diagram 23 | #[doc = mermaid!("../../../doc/text/decode_csv.seq.mmd" framed)] 24 | /// 25 | /// # Examples 26 | /// 27 | /// ## Using the block in a system 28 | /// 29 | /// ```no_run 30 | /// # use protoflow_blocks::*; 31 | /// # fn main() { 32 | /// System::build(|s| { 33 | /// // TODO 34 | /// }); 35 | /// # } 36 | /// ``` 37 | /// 38 | /// ## Running the block via the CLI 39 | /// 40 | /// ```console 41 | /// $ protoflow execute DecodeCSV 42 | /// ``` 43 | /// 44 | #[derive(Block, Clone)] 45 | pub struct DecodeCsv { 46 | /// The input message bytes stream. 47 | #[input] 48 | pub input: InputPort, 49 | /// The csv header message. 50 | #[output] 51 | pub header: OutputPort, 52 | /// The csv rows message stream. 53 | #[output] 54 | pub rows: OutputPort, 55 | // TODO for the future to add a delimiter parameter. 56 | } 57 | 58 | impl DecodeCsv { 59 | pub fn new(input: InputPort, header: OutputPort, rows: OutputPort) -> Self { 60 | Self { input, header, rows } 61 | } 62 | 63 | pub fn with_system(system: &System) -> Self { 64 | use crate::SystemBuilding; 65 | Self::new(system.input(), system.output(), system.output()) 66 | } 67 | } 68 | 69 | impl Block for DecodeCsv { 70 | fn execute(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 71 | while let Some(input) = self.input.recv()? { 72 | let cursor = Cursor::new(input); 73 | let mut rdr = ReaderBuilder::new() 74 | .has_headers(true) 75 | .from_reader(cursor); 76 | 77 | let headers = rdr.headers().map_err(|e| BlockError::Other(e.to_string()))? 78 | .iter() 79 | .map(|h| Value { 80 | kind: Some(StringValue(h.to_string())), 81 | }) 82 | .collect(); 83 | 84 | let header_output = Value { 85 | kind: Some(ListValue(LV { values: headers })) 86 | }; 87 | 88 | self.header.send(&header_output)?; 89 | 90 | for result in rdr.records() { 91 | let record = result.map_err(|e| BlockError::Other(e.to_string()))?; 92 | let row_values = record.iter() 93 | .map(|v| Value { 94 | kind: Some(StringValue(v.to_string())), 95 | }) 96 | .collect(); 97 | 98 | let row_output = Value { 99 | kind: Some(ListValue(LV { values: row_values })) 100 | }; 101 | 102 | self.rows.send(&row_output)?; 103 | } 104 | } 105 | 106 | Ok(()) 107 | } 108 | } 109 | 110 | #[cfg(feature = "std")] 111 | impl StdioSystem for DecodeCsv { 112 | fn build_system(config: StdioConfig) -> Result { 113 | 114 | config.reject_any()?; 115 | 116 | Ok(System::build(|_s| { todo!() })) 117 | } 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | 123 | use super::DecodeCsv; 124 | use crate::{System, SystemBuilding}; 125 | 126 | #[test] 127 | fn instantiate_block() { 128 | // Check that the block is constructible: 129 | let _ = System::build(|s| { 130 | let _ = s.block(DecodeCsv::new(s.input(), s.output(), s.output())); 131 | }); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/text.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | pub mod text { 4 | use super::{ 5 | prelude::{vec, Box, Cow, Named, Vec, String}, 6 | BlockConnections, BlockInstantiation, InputPortName, OutputPortName, System 7 | }; 8 | use protoflow_core::Block; 9 | 10 | pub trait TextBlocks { 11 | fn concat_strings(&mut self) -> ConcatStrings; 12 | fn concat_strings_by(&mut self, delimiter: &str) -> ConcatStrings; 13 | fn decode_csv(&mut self) -> DecodeCsv; 14 | fn encode_csv(&mut self) -> EncodeCsv; 15 | fn split_string(&mut self, delimiter: &str) -> SplitString; 16 | } 17 | 18 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 19 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 20 | pub enum TextBlockTag { 21 | ConcatStrings, 22 | DecodeCsv, 23 | EncodeCsv, 24 | SplitString, 25 | } 26 | 27 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 28 | #[derive(Clone, Debug)] 29 | pub enum TextBlockConfig { 30 | ConcatStrings { 31 | input: InputPortName, 32 | output: OutputPortName, 33 | delimiter: Option 34 | }, 35 | #[cfg_attr(feature = "serde", serde(rename = "DecodeCSV"))] 36 | DecodeCsv { 37 | input: InputPortName, 38 | header: OutputPortName, 39 | rows: OutputPortName, 40 | }, 41 | #[cfg_attr(feature = "serde", serde(rename = "EncodeCSV"))] 42 | EncodeCsv { 43 | header: InputPortName, 44 | rows: InputPortName, 45 | output: OutputPortName, 46 | }, 47 | SplitString { 48 | input: InputPortName, 49 | output: OutputPortName, 50 | delimiter: Option 51 | }, 52 | } 53 | 54 | impl Named for TextBlockConfig { 55 | fn name(&self) -> Cow { 56 | use TextBlockConfig::*; 57 | Cow::Borrowed(match self { 58 | ConcatStrings { .. } => "ConcatStrings", 59 | DecodeCsv { .. } => "DecodeCSV", 60 | EncodeCsv { .. } => "EncodeCSV", 61 | SplitString { .. } => "SplitString", 62 | }) 63 | } 64 | } 65 | 66 | impl BlockConnections for TextBlockConfig { 67 | fn output_connections(&self) -> Vec<(&'static str, Option)> { 68 | use TextBlockConfig::*; 69 | match self { 70 | ConcatStrings { output, .. } 71 | | EncodeCsv { output, .. } 72 | | SplitString { output, .. } => { 73 | vec![("output", Some(output.clone()))] 74 | } 75 | DecodeCsv { header, rows, .. } => { 76 | vec![("header", Some(header.clone())), ("rows", Some(rows.clone()))] 77 | } 78 | } 79 | } 80 | } 81 | 82 | impl BlockInstantiation for TextBlockConfig { 83 | fn instantiate(&self, system: &mut System) -> Box { 84 | use TextBlockConfig::*; 85 | match self { 86 | ConcatStrings { delimiter, .. } => { 87 | Box::new(super::ConcatStrings::with_system(system, delimiter.clone())) 88 | } 89 | DecodeCsv { .. } => { 90 | Box::new(super::DecodeCsv::with_system(system)) 91 | } 92 | EncodeCsv { .. } => { 93 | Box::new(super::EncodeCsv::with_system(system)) 94 | } 95 | SplitString { delimiter, .. } => { 96 | Box::new(super::SplitString::with_system(system, delimiter.clone())) 97 | } 98 | } 99 | } 100 | } 101 | 102 | mod concat_strings; 103 | pub use concat_strings::*; 104 | 105 | mod decode_csv; 106 | pub use decode_csv::*; 107 | 108 | mod encode_csv; 109 | pub use encode_csv::*; 110 | 111 | mod split_string; 112 | pub use split_string::*; 113 | } 114 | 115 | pub use text::*; 116 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/sys/read_stdin.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{vec, Bytes}, 7 | types::ByteSize, 8 | StdioConfig, StdioError, StdioSystem, System, 9 | }; 10 | use protoflow_core::{Block, BlockResult, BlockRuntime, OutputPort}; 11 | use protoflow_derive::Block; 12 | use simple_mermaid::mermaid; 13 | use std::io::Read; 14 | 15 | /// The default buffer size for reading from standard input. 16 | const DEFAULT_BUFFER_SIZE: ByteSize = ByteSize::new(1024); 17 | 18 | /// A block that reads bytes from standard input (aka stdin). 19 | /// 20 | /// # Block Diagram 21 | #[doc = mermaid!("../../../doc/sys/read_stdin.mmd")] 22 | /// 23 | /// # Sequence Diagram 24 | #[doc = mermaid!("../../../doc/sys/read_stdin.seq.mmd" framed)] 25 | /// 26 | /// # Examples 27 | /// 28 | /// ## Using the block in a system 29 | /// 30 | /// ```rust 31 | /// # use protoflow_blocks::*; 32 | /// # fn main() { 33 | /// System::build(|s| { 34 | /// let stdin = s.read_stdin(); 35 | /// let stdout = s.write_stdout(); 36 | /// s.connect(&stdin.output, &stdout.input); 37 | /// }); 38 | /// # } 39 | /// ``` 40 | /// 41 | /// ## Running the block via the CLI 42 | /// 43 | /// ```console 44 | /// $ protoflow execute ReadStdin < input.txt 45 | /// ``` 46 | /// 47 | /// ```console 48 | /// $ protoflow execute ReadStdin buffer-size=1024 < input.txt 49 | /// ``` 50 | /// 51 | #[derive(Block, Clone)] 52 | pub struct ReadStdin { 53 | /// The output message stream. 54 | #[output] 55 | pub output: OutputPort, 56 | 57 | /// The maximum number of bytes to read at a time. 58 | #[parameter] 59 | pub buffer_size: ByteSize, 60 | } 61 | 62 | impl ReadStdin { 63 | pub fn new(output: OutputPort) -> Self { 64 | Self::with_params(output, None) 65 | } 66 | 67 | pub fn with_params(output: OutputPort, buffer_size: Option) -> Self { 68 | Self { 69 | output, 70 | buffer_size: buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE), 71 | } 72 | } 73 | 74 | pub fn with_system(system: &System, buffer_size: Option) -> Self { 75 | use crate::SystemBuilding; 76 | Self::with_params(system.output(), buffer_size) 77 | } 78 | } 79 | 80 | impl Block for ReadStdin { 81 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 82 | let stdin = std::io::stdin().lock(); 83 | let mut reader = std::io::BufReader::new(stdin); 84 | let mut buffer = vec![0; self.buffer_size.into()]; 85 | 86 | runtime.wait_for(&self.output)?; 87 | 88 | loop { 89 | buffer.resize(self.buffer_size.into(), b'\0'); // reinitialize the buffer 90 | buffer.fill(b'\0'); 91 | 92 | match reader.read(&mut buffer) { 93 | Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue, 94 | Err(err) => return Err(err.into()), 95 | Ok(0) => break, // EOF 96 | Ok(buffer_len) => { 97 | buffer.resize(buffer_len, b'\0'); // truncate the buffer 98 | let bytes = Bytes::from(buffer.clone()); 99 | self.output.send(&bytes)?; 100 | } 101 | } 102 | } 103 | 104 | Ok(()) 105 | } 106 | } 107 | 108 | #[cfg(feature = "std")] 109 | impl StdioSystem for ReadStdin { 110 | fn build_system(config: StdioConfig) -> Result { 111 | use crate::SystemBuilding; 112 | 113 | config.allow_only(vec!["buffer_size"])?; 114 | 115 | Ok(System::build(|s| { 116 | let stdin = config.read_stdin(s); 117 | let stdout = config.write_stdout(s); 118 | s.connect(&stdin.output, &stdout.input); 119 | })) 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use super::ReadStdin; 126 | use crate::{System, SystemBuilding}; 127 | 128 | #[test] 129 | fn instantiate_block() { 130 | // Check that the block is constructible: 131 | let _ = System::build(|s| { 132 | let _ = s.block(ReadStdin::new(s.output())); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/port_id.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::prelude::{fmt, TryFrom}; 4 | 5 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 6 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 7 | #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] 8 | pub enum PortID { 9 | Input(InputPortID), 10 | Output(OutputPortID), 11 | } 12 | 13 | impl PortID { 14 | pub fn as_isize(&self) -> isize { 15 | match self { 16 | PortID::Input(id) => id.0, 17 | PortID::Output(id) => id.0, 18 | } 19 | } 20 | 21 | pub fn as_usize(&self) -> usize { 22 | self.as_isize() as _ 23 | } 24 | } 25 | 26 | impl TryFrom for PortID { 27 | type Error = &'static str; 28 | 29 | fn try_from(id: isize) -> Result { 30 | if id < 0 { 31 | Ok(Self::Input(InputPortID(id))) 32 | } else if id > 0 { 33 | Ok(Self::Output(OutputPortID(id))) 34 | } else { 35 | Err("Port ID cannot be zero") 36 | } 37 | } 38 | } 39 | 40 | impl From for PortID { 41 | fn from(port_id: InputPortID) -> PortID { 42 | PortID::Input(port_id) 43 | } 44 | } 45 | 46 | impl From for PortID { 47 | fn from(port_id: OutputPortID) -> PortID { 48 | PortID::Output(port_id) 49 | } 50 | } 51 | 52 | impl From for isize { 53 | fn from(port_id: PortID) -> isize { 54 | port_id.as_isize() 55 | } 56 | } 57 | 58 | impl From for usize { 59 | fn from(port_id: PortID) -> usize { 60 | port_id.as_usize() 61 | } 62 | } 63 | 64 | impl fmt::Display for PortID { 65 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 66 | match self { 67 | PortID::Input(id) => write!(f, "{}", id), 68 | PortID::Output(id) => write!(f, "{}", id), 69 | } 70 | } 71 | } 72 | 73 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 74 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 75 | pub struct InputPortID(pub(crate) isize); 76 | 77 | impl InputPortID { 78 | #[doc(hidden)] 79 | pub fn index(&self) -> usize { 80 | self.0.unsigned_abs() - 1 81 | } 82 | } 83 | 84 | impl TryFrom for InputPortID { 85 | type Error = &'static str; 86 | 87 | fn try_from(id: isize) -> Result { 88 | if id < 0 { 89 | Ok(InputPortID(id)) 90 | } else { 91 | Err("Input port IDs must be negative integers") 92 | } 93 | } 94 | } 95 | 96 | impl From for isize { 97 | fn from(id: InputPortID) -> isize { 98 | id.0 99 | } 100 | } 101 | 102 | impl From for usize { 103 | fn from(id: InputPortID) -> usize { 104 | id.0.unsigned_abs() 105 | } 106 | } 107 | 108 | impl fmt::Display for InputPortID { 109 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 110 | write!(f, "{}", self.0) 111 | } 112 | } 113 | 114 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 115 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 116 | pub struct OutputPortID(pub(crate) isize); 117 | 118 | impl OutputPortID { 119 | #[doc(hidden)] 120 | pub fn index(&self) -> usize { 121 | (self.0 as usize) - 1 122 | } 123 | } 124 | 125 | impl TryFrom for OutputPortID { 126 | type Error = &'static str; 127 | 128 | fn try_from(id: isize) -> Result { 129 | if id > 0 { 130 | Ok(OutputPortID(id)) 131 | } else { 132 | Err("Output port IDs must be positive integers") 133 | } 134 | } 135 | } 136 | 137 | impl From for isize { 138 | fn from(id: OutputPortID) -> isize { 139 | id.0 140 | } 141 | } 142 | 143 | impl From for usize { 144 | fn from(id: OutputPortID) -> usize { 145 | id.0 as usize 146 | } 147 | } 148 | 149 | impl fmt::Display for OutputPortID { 150 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 151 | write!(f, "{}", self.0) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/protoflow-core/src/output_port.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | use crate::{ 4 | prelude::{fmt, Arc, Bytes, Cow, MaybeLabeled, MaybeNamed, PhantomData, RwLock}, 5 | Message, MessageSender, OutputPortID, Port, PortError, PortID, PortResult, PortState, System, 6 | Transport, 7 | }; 8 | 9 | #[derive(Clone)] //, Debug, Eq, Ord, PartialEq, PartialOrd)] 10 | pub struct OutputPort { 11 | pub(crate) state: Arc>, 12 | _phantom: PhantomData, 13 | } 14 | 15 | impl OutputPort { 16 | pub fn new(system: &System) -> Self { 17 | let id = system.connection_config.borrow_mut().add_output(); 18 | let connection = Default::default(); 19 | let state = Arc::new(RwLock::new(OutputPortState { id, connection })); 20 | Self { 21 | _phantom: PhantomData, 22 | state, 23 | } 24 | } 25 | 26 | pub fn close(&mut self) -> PortResult { 27 | let mut state = self.state.write(); 28 | let OutputPortConnection::Running(ref transport) = state.connection else { 29 | return Ok(false); 30 | }; 31 | transport.close(PortID::Output(state.id))?; 32 | state.connection = OutputPortConnection::Closed; 33 | Ok(true) 34 | } 35 | 36 | pub fn send<'a>(&self, message: impl Into<&'a T>) -> PortResult<()> 37 | where 38 | T: 'a, 39 | { 40 | let state = self.state.read(); 41 | let OutputPortConnection::Running(ref transport) = state.connection else { 42 | return Err(PortError::Disconnected); 43 | }; 44 | let message: &T = message.into(); 45 | let bytes = Bytes::from(message.encode_length_delimited_to_vec()); 46 | transport.send(state.id, bytes) 47 | } 48 | } 49 | 50 | impl MaybeNamed for OutputPort { 51 | fn name(&self) -> Option> { 52 | None // TODO 53 | } 54 | } 55 | 56 | impl MaybeLabeled for OutputPort { 57 | fn label(&self) -> Option> { 58 | None // TODO 59 | } 60 | } 61 | 62 | impl Port for OutputPort { 63 | fn id(&self) -> PortID { 64 | PortID::Output(self.state.read().id) 65 | } 66 | 67 | fn state(&self) -> PortState { 68 | let state = self.state.read(); 69 | match state.connection { 70 | OutputPortConnection::Closed => PortState::Closed, 71 | OutputPortConnection::Ready => PortState::Open, 72 | OutputPortConnection::Running(ref transport) => transport 73 | .state(PortID::Output(state.id)) 74 | .unwrap_or(PortState::Closed), 75 | } 76 | } 77 | 78 | fn close(&mut self) -> PortResult { 79 | OutputPort::close(self) 80 | } 81 | } 82 | 83 | impl MessageSender for OutputPort { 84 | fn send<'a>(&self, message: impl Into<&'a T>) -> PortResult<()> 85 | where 86 | T: 'a, 87 | { 88 | OutputPort::send(self, message) 89 | } 90 | } 91 | 92 | impl fmt::Display for OutputPort { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 94 | write!(f, "{}→", self.id()) 95 | } 96 | } 97 | 98 | impl fmt::Debug for OutputPort { 99 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 100 | f.debug_struct("OutputPort") 101 | .field("state", &self.state.read()) 102 | .finish() 103 | } 104 | } 105 | 106 | #[derive(Clone, Debug)] 107 | pub(crate) struct OutputPortState { 108 | pub(crate) id: OutputPortID, 109 | pub(crate) connection: OutputPortConnection, 110 | } 111 | 112 | #[derive(Clone, Default)] 113 | pub(crate) enum OutputPortConnection { 114 | #[default] 115 | Ready, 116 | Running(Arc), 117 | Closed, 118 | } 119 | 120 | impl core::fmt::Debug for OutputPortConnection { 121 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 122 | write!( 123 | f, 124 | "OutputPortConnection::{}", 125 | match self { 126 | Self::Ready => "Ready", 127 | Self::Running(_) => "Running", 128 | Self::Closed => "Closed", 129 | } 130 | ) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/io/encode.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{vec, Bytes, String, ToString}, 7 | types::Encoding, 8 | StdioConfig, StdioError, StdioSystem, System, 9 | }; 10 | use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort, Message, OutputPort}; 11 | use protoflow_derive::Block; 12 | use simple_mermaid::mermaid; 13 | 14 | /// A block that encodes `T` messages to a byte stream. 15 | /// 16 | /// # Block Diagram 17 | #[doc = mermaid!("../../../doc/io/encode.mmd")] 18 | /// 19 | /// # Sequence Diagram 20 | #[doc = mermaid!("../../../doc/io/encode.seq.mmd" framed)] 21 | /// 22 | /// # Examples 23 | /// 24 | /// ## Using the block in a system 25 | /// 26 | /// ```rust 27 | /// # use protoflow_blocks::*; 28 | /// # fn main() { 29 | /// System::build(|s| { 30 | /// let stdin = s.read_stdin(); 31 | /// let message_decoder = s.decode_lines(); 32 | /// let counter = s.count::(); 33 | /// let count_encoder = s.encode_lines(); 34 | /// let stdout = s.write_stdout(); 35 | /// s.connect(&stdin.output, &message_decoder.input); 36 | /// s.connect(&message_decoder.output, &counter.input); 37 | /// s.connect(&counter.count, &count_encoder.input); 38 | /// s.connect(&count_encoder.output, &stdout.input); 39 | /// }); 40 | /// # } 41 | /// ``` 42 | /// 43 | /// ## Running the block via the CLI 44 | /// 45 | /// ```console 46 | /// $ protoflow execute Encode encoding=text 47 | /// ``` 48 | /// 49 | /// ```console 50 | /// $ protoflow execute Encode encoding=protobuf 51 | /// ``` 52 | /// 53 | #[derive(Block, Clone)] 54 | pub struct Encode { 55 | /// The input message stream. 56 | #[input] 57 | pub input: InputPort, 58 | 59 | /// The output byte stream. 60 | #[output] 61 | pub output: OutputPort, 62 | 63 | /// A configuration parameter for how to encode messages. 64 | #[parameter] 65 | pub encoding: Encoding, 66 | } 67 | 68 | impl Encode { 69 | pub fn new(input: InputPort, output: OutputPort) -> Self { 70 | Self::with_params(input, output, None) 71 | } 72 | 73 | pub fn with_params( 74 | input: InputPort, 75 | output: OutputPort, 76 | encoding: Option, 77 | ) -> Self { 78 | Self { 79 | input, 80 | output, 81 | encoding: encoding.unwrap_or_default(), 82 | } 83 | } 84 | } 85 | 86 | impl Encode { 87 | pub fn with_system(system: &System, encoding: Option) -> Self { 88 | use crate::SystemBuilding; 89 | Self::with_params(system.input(), system.output(), encoding) 90 | } 91 | } 92 | 93 | impl Block for Encode { 94 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 95 | runtime.wait_for(&self.input)?; 96 | 97 | while let Some(message) = self.input.recv()? { 98 | use Encoding::*; 99 | let bytes = match self.encoding { 100 | ProtobufWithLengthPrefix => Bytes::from(message.encode_length_delimited_to_vec()), 101 | ProtobufWithoutLengthPrefix => Bytes::from(message.encode_to_vec()), 102 | TextWithNewlineSuffix => { 103 | let mut string = message.to_string(); 104 | string.push('\n'); 105 | Bytes::from(string) 106 | } 107 | }; 108 | self.output.send(&bytes)?; 109 | } 110 | 111 | Ok(()) 112 | } 113 | } 114 | 115 | #[cfg(feature = "std")] 116 | impl StdioSystem for Encode { 117 | fn build_system(config: StdioConfig) -> Result { 118 | //use crate::{CoreBlocks, SysBlocks, SystemBuilding}; 119 | 120 | config.allow_only(vec!["encoding"])?; 121 | 122 | Ok(System::build(|_s| todo!())) 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::Encode; 129 | use crate::{System, SystemBuilding}; 130 | 131 | #[test] 132 | fn instantiate_block() { 133 | // Check that the block is constructible: 134 | let _ = System::build(|s| { 135 | let _ = s.block(Encode::::new(s.input(), s.output())); 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/sys/read_file.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{vec, Bytes, String, Vec}, 7 | StdioConfig, StdioError, StdioSystem, System, 8 | }; 9 | use protoflow_core::{Block, BlockResult, BlockRuntime, InputPort, OutputPort}; 10 | use protoflow_derive::Block; 11 | use simple_mermaid::mermaid; 12 | 13 | /// A block that reads bytes from the contents of a file. 14 | /// 15 | /// # Block Diagram 16 | #[doc = mermaid!("../../../doc/sys/read_file.mmd")] 17 | /// 18 | /// # Sequence Diagram 19 | #[doc = mermaid!("../../../doc/sys/read_file.seq.mmd" framed)] 20 | /// 21 | /// # Examples 22 | /// 23 | /// ## Using the block in a system 24 | /// 25 | /// ```rust 26 | /// # use protoflow_blocks::*; 27 | /// # fn main() { 28 | /// System::build(|s| { 29 | /// // TODO 30 | /// }); 31 | /// # } 32 | /// ``` 33 | /// 34 | /// ## Running the block via the CLI 35 | /// 36 | /// ```console 37 | /// $ protoflow execute ReadFile path=/tmp/file.txt 38 | /// ``` 39 | /// 40 | #[derive(Block, Clone)] 41 | pub struct ReadFile { 42 | /// The path to the file to read from. 43 | #[input] 44 | pub path: InputPort, 45 | 46 | /// The output message stream. 47 | #[output] 48 | pub output: OutputPort, 49 | } 50 | 51 | impl ReadFile { 52 | pub fn new(path: InputPort, output: OutputPort) -> Self { 53 | Self { path, output } 54 | } 55 | 56 | pub fn with_system(system: &System) -> Self { 57 | use crate::SystemBuilding; 58 | Self::new(system.input(), system.output()) 59 | } 60 | } 61 | 62 | impl Block for ReadFile { 63 | fn execute(&mut self, _runtime: &dyn BlockRuntime) -> BlockResult { 64 | use std::io::prelude::Read; 65 | 66 | while let Some(path) = self.path.recv()? { 67 | let mut file = std::fs::OpenOptions::new().read(true).open(path)?; 68 | 69 | let mut buffer = Vec::new(); 70 | file.read_to_end(&mut buffer)?; 71 | let bytes = Bytes::from(buffer); 72 | 73 | self.output.send(&bytes)?; 74 | } 75 | 76 | Ok(()) 77 | } 78 | } 79 | 80 | #[cfg(feature = "std")] 81 | impl StdioSystem for ReadFile { 82 | fn build_system(config: StdioConfig) -> Result { 83 | //use crate::{CoreBlocks, SysBlocks, SystemBuilding}; 84 | 85 | config.allow_only(vec!["path"])?; 86 | 87 | Ok(System::build(|_s| todo!())) // TODO 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | extern crate std; 94 | 95 | use super::ReadFile; 96 | use crate::{System, SystemBuilding, SystemExecution}; 97 | 98 | #[test] 99 | fn instantiate_block() { 100 | // Check that the block is constructible: 101 | let _ = System::build(|s| { 102 | let _ = s.block(ReadFile::new(s.input(), s.output())); 103 | }); 104 | } 105 | 106 | #[test] 107 | fn run_block() { 108 | use protoflow_core::{ 109 | runtimes::StdRuntime as Runtime, transports::MpscTransport as Transport, 110 | }; 111 | use std::io::Write; 112 | 113 | let mut temp_file = tempfile::NamedTempFile::new().unwrap(); 114 | let test_content = "Hello, World!\n"; 115 | temp_file.write_all(test_content.as_bytes()).unwrap(); 116 | 117 | let mut system = System::new(&Runtime::new(Transport::new()).unwrap()); 118 | let read_file = system.block(ReadFile::with_system(&system)); 119 | 120 | let mut path = system.output(); 121 | let output = system.input(); 122 | 123 | system.connect(&path, &read_file.path); 124 | system.connect(&read_file.output, &output); 125 | 126 | let process = system.execute().unwrap(); 127 | 128 | path.send(&temp_file.path().to_string_lossy().into()) 129 | .unwrap(); 130 | 131 | assert_eq!( 132 | output 133 | .recv() 134 | .expect("should receive output") 135 | .expect("output shouldn't be None"), 136 | test_content 137 | ); 138 | 139 | path.close().unwrap(); 140 | 141 | assert_eq!( 142 | output.recv(), 143 | Ok(None), 144 | "want EOS signal after path port is closed" 145 | ); 146 | 147 | process.join().unwrap(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/protoflow-blocks/src/blocks/text/split_string.rs: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | 3 | extern crate std; 4 | 5 | use crate::{ 6 | prelude::{String, ToString, vec}, 7 | StdioConfig, StdioError, StdioSystem, System, 8 | }; 9 | use protoflow_core::{ 10 | Block, BlockResult, BlockRuntime, InputPort, OutputPort, 11 | }; 12 | use protoflow_derive::Block; 13 | use simple_mermaid::mermaid; 14 | 15 | /// A block that splits string. 16 | /// 17 | /// # Block Diagram 18 | #[doc = mermaid!("../../../doc/text/split_string.mmd")] 19 | /// 20 | /// # Sequence Diagram 21 | #[doc = mermaid!("../../../doc/text/split_string.seq.mmd" framed)] 22 | /// 23 | /// # Examples 24 | /// 25 | /// ## Using the block in a system 26 | /// 27 | /// ```rust 28 | /// # use protoflow_blocks::*; 29 | /// # fn main() { 30 | /// System::build(|s| { 31 | /// let config = StdioConfig { 32 | /// encoding: Default::default(), 33 | /// params: Default::default(), 34 | /// }; 35 | /// let delimiter = " "; 36 | /// let stdin = s.read_stdin(); 37 | /// let line_decoder = s.decode_with(config.encoding); 38 | /// let split_string = s.split_string(delimiter); 39 | /// let line_encoder = s.encode_with(config.encoding); 40 | /// let stdout = config.write_stdout(s); 41 | /// s.connect(&stdin.output, &line_decoder.input); 42 | /// s.connect(&line_decoder.output, &split_string.input); 43 | /// s.connect(&split_string.output, &line_encoder.input); 44 | /// s.connect(&line_encoder.output, &stdout.input); 45 | /// }); 46 | /// # } 47 | /// ``` 48 | /// 49 | /// ## Running the block via the CLI 50 | /// 51 | /// ```console 52 | /// $ protoflow execute SplitString delimiter=" " 53 | /// ``` 54 | /// 55 | #[derive(Block, Clone)] 56 | pub struct SplitString { 57 | /// The input message string stream. 58 | #[input] 59 | pub input: InputPort, 60 | /// The output message string stream. 61 | #[output] 62 | pub output: OutputPort, 63 | /// A parameter to split the input string 64 | #[parameter] 65 | pub delimiter: String 66 | } 67 | 68 | impl SplitString { 69 | pub fn new(input: InputPort, output: OutputPort) -> Self { 70 | Self::with_params(input, output, None) 71 | } 72 | 73 | pub fn with_system(system: &System, delimiter: Option) -> Self { 74 | use crate::SystemBuilding; 75 | Self::with_params(system.input(), system.output(), delimiter) 76 | } 77 | 78 | pub fn with_params(input: InputPort, output: OutputPort, delimiter: Option) -> Self { 79 | Self { 80 | input, 81 | output, 82 | delimiter: delimiter.unwrap_or_default() 83 | } 84 | } 85 | } 86 | 87 | impl Block for SplitString { 88 | fn execute(&mut self, runtime: &dyn BlockRuntime) -> BlockResult { 89 | runtime.wait_for(&self.input)?; 90 | 91 | while let Some(input) = self.input.recv()? { 92 | for output in input.split(&self.delimiter) { 93 | self.output.send(&output.to_string())?; 94 | } 95 | } 96 | 97 | Ok(()) 98 | } 99 | } 100 | 101 | #[cfg(feature = "std")] 102 | impl StdioSystem for SplitString { 103 | fn build_system(config: StdioConfig) -> Result { 104 | use crate::{TextBlocks, IoBlocks, SysBlocks, SystemBuilding}; 105 | 106 | config.allow_only(vec!["delimiter"])?; 107 | let delimiter = config.get_string("delimiter")?; 108 | 109 | Ok(System::build(|s| { 110 | let stdin = s.read_stdin(); 111 | let line_decoder = s.decode_with(config.encoding); 112 | let split_string = s.split_string(&delimiter); 113 | let line_encoder = s.encode_with(config.encoding); 114 | let stdout = config.write_stdout(s); 115 | s.connect(&stdin.output, &line_decoder.input); 116 | s.connect(&line_decoder.output, &split_string.input); 117 | s.connect(&split_string.output, &line_encoder.input); 118 | s.connect(&line_encoder.output, &stdout.input); 119 | })) 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | 126 | use super::SplitString; 127 | use crate::{System, SystemBuilding}; 128 | 129 | #[test] 130 | fn instantiate_block() { 131 | // Check that the block is constructible: 132 | let _ = System::build(|s| { 133 | let _ = s.block(SplitString::new(s.input(), s.output())); 134 | }); 135 | } 136 | } 137 | --------------------------------------------------------------------------------