1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#![deny(missing_docs)]
#![warn(clippy::unwrap_used)]
//! `flowstdlib` is a library of flows and functions that can be used from flows.
//!
//! The flow and function definition are used at compile time when compile flows that reference
//! it.
//!
//! At run-time, library entries can be of two types, indicated by their
//! [ImplementationLocator][flowcore::model::lib_manifest::ImplementationLocator]
//! - [Native][flowcore::model::lib_manifest::ImplementationLocator::Native] - a native binary
//! (containing) all functions is built and linked by a flow
//! runner program (e.g. `flowr`) so that any function referenced by a flow is executed natively
//! at native speeds. `flowr` offers the user control using this via the `-n, --native`
//! command line option.
//! - [RelativePath][flowcore::model::lib_manifest::ImplementationLocator::RelativePath] - functions
//! are compiled to WASM files and located within the library at runtime by the flow runner using
//! this file path relative to the lib root. If either the library if not linked natively, or the
//! `-n, --native` command line option is not used, when the function is referenced by a flow being
//! run, it is loaded and executed in a WASM runtime.

/// Functions and flows to control the flow of data in a flow based on control inputs.
pub mod control;

/// Some generic processes that act on data.
pub mod data;

/// Math Functions and flows
pub mod math;

/// Functions for the formatting of values and conversion from one type to another.
pub mod fmt;

/// Use [manifest::get_manifest] to get the natively/statically linked
/// [LibraryManifest][flowcore::model::lib_manifest::LibraryManifest] for this library
/// to get access to everything `error_chain` creates.
pub mod manifest;

/// provides [Error][errors::Error] that other modules in this crate will `use errors::*;`
pub mod errors;

#[cfg(test)]
pub mod test {
    use std::env;
    use std::fs::File;
    use std::io::{Read, Write};
    use std::path::PathBuf;
    use std::process::{Command, Stdio};

    use tempdir::TempDir;

    pub fn get_context_root() -> PathBuf {
        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let samples_dir = manifest_dir.parent().ok_or("Could not get parent dir")
            .expect("Could not get parent dir");
        samples_dir.join("flowr/src/bin/flowrcli/context")
    }

    fn execute_flow(filepath: PathBuf) -> String {
        let mut command = Command::new("flowc");
        let context_root = get_context_root();
        let command_args = vec![
            "-C", context_root.to_str().expect("Could not get context root"),
            filepath.to_str().expect("Couldn't convert file path to string")];

        // spawn the 'flowc' child process
        let mut runner = command
            .args(command_args)
            .stdin(Stdio::inherit())
            .stdout(Stdio::piped())
            .stderr(Stdio::inherit())
            .spawn()
            .expect("Couldn't spawn flowc to run test flow");

        let result = runner.wait().expect("failed to wait on child");

        // read it's stdout
        let mut output = String::new();
        if let Some(ref mut stdout) = runner.stdout {
            stdout.read_to_string(&mut output).expect("Could not read stdout");
        }

        assert!(result.success(), );
        output
    }

    #[test]
    fn test_range_flow() {
        let flow = "\
flow = \"range_test\"

[[process]]
source = \"lib://flowstdlib/math/range\"
input.range = { once = [1, 10] }

[[process]]
source = \"context://stdio/stdout\"

[[connection]]
from = \"range/number\"
to = \"stdout\"
";

        let temp_dir = TempDir::new("flow").expect("Could not create TempDir").into_path();
        let flow_filename = temp_dir.join("range_test.toml");
        let mut flow_file =
            File::create(&flow_filename).expect("Could not create lib manifest file");
        flow_file.write_all(flow.as_bytes()).expect("Could not write data bytes to created flow file");

        let stdout = execute_flow(flow_filename);

        let mut numbers: Vec<i32> = stdout.lines().map(|l| l.parse::<i32>().expect("Not a number")).collect::<Vec<i32>>();
        numbers.sort_unstable();
        assert_eq!(numbers, vec!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }

    #[test]
    fn test_sequence_flow() {
        let flow = "\
flow = \"sequence_test\"

[[process]]
source = \"lib://flowstdlib/math/sequence\"
input.start = { once = 1 }
input.limit = { once = 10 }
input.step = { once = 1 }

[[process]]
source = \"context://stdio/stdout\"

[[connection]]
from = \"sequence/sequence\"
to = \"stdout\"
";

        let temp_dir = TempDir::new("flow").expect("Could not create TempDir").into_path();
        let flow_filename = temp_dir.join("sequence_test.toml");
        let mut flow_file =
            File::create(&flow_filename).expect("Could not create lib manifest file");
        flow_file.write_all(flow.as_bytes()).expect("Could not write data bytes to created flow file");

        let stdout = execute_flow(flow_filename);

        let numbers: Vec<i32> = stdout.lines().map(|l| l.parse::<i32>().expect("Not a number")).collect::<Vec<i32>>();
        assert_eq!(numbers, vec!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }

}