#![deny(missing_docs)]
#![warn(clippy::unwrap_used)]
#![allow(clippy::result_large_err)]
use std::env;
use std::path::PathBuf;
use std::process::exit;
use clap::{Arg, ArgMatches, Command};
use colored::*;
use log::{debug, error, info, warn};
use simpath::Simpath;
use simplog::SimpleLogger;
use url::Url;
use errors::*;
use flowclib::info;
use flowcore::meta_provider::MetaProvider;
use flowcore::url_helper::url_from_string;
use lib_build::build_lib;
use crate::flow_compile::compile_and_execute_flow;
pub mod errors;
mod flow_compile;
pub mod lib_build;
mod source_arg;
pub struct Options {
lib: bool,
source_url: Url,
flow_args: Vec<String>,
graphs: bool,
execution_metrics: bool,
wasm_execution: bool,
compile_only: bool,
debug_symbols: bool,
provided_implementations: bool,
output_dir: PathBuf,
stdin_file: Option<String>,
lib_dirs: Vec<String>,
native_only: bool,
context_root: Option<PathBuf>,
verbosity: Option<String>,
optimize: bool,
}
fn main() {
match run() {
Err(ref e) => {
error!("{}: {}", "error".red(), e);
for e in e.iter().skip(1) {
error!("caused by: {}", e);
}
if let Some(backtrace) = e.backtrace() {
error!("backtrace: {:?}", backtrace);
}
exit(1);
}
Ok(_) => exit(0)
}
}
fn get_lib_search_path(search_path_additions: &[String]) -> Result<Simpath> {
let mut lib_search_path = Simpath::new_with_separator("FLOW_LIB_PATH", ',');
if env::var("FLOW_LIB_PATH").is_err() && search_path_additions.is_empty() {
warn!(
"'FLOW_LIB_PATH' env var not set and no LIB_DIRS options supplied. Library references may not be found."
);
}
for addition in search_path_additions {
lib_search_path.add(addition);
info!("'{}' added to the Library Search Path", addition);
}
Ok(lib_search_path)
}
fn run() -> Result<()> {
let options = parse_args(get_matches())?;
let lib_search_path = get_lib_search_path(&options.lib_dirs)?;
let context_root = options.context_root.clone().unwrap_or_else(|| PathBuf::from(""));
let provider = &MetaProvider::new(lib_search_path, context_root);
if options.lib {
build_lib(&options, provider).chain_err(|| "Could not build library")
} else {
compile_and_execute_flow(&options, provider)
}
}
fn get_matches() -> ArgMatches {
let app = Command::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"));
#[cfg(feature = "debugger")]
let app = app.arg(
Arg::new("debug")
.short('d')
.long("debug")
.action(clap::ArgAction::SetTrue)
.help("Generate symbols for debugging. If executing the flow, do so with the debugger"),
);
let app = app
.arg(
Arg::new("compile")
.short('c')
.long("compile")
.action(clap::ArgAction::SetTrue)
.help("Compile the flow and implementations, but do not execute"),
)
.arg(
Arg::new("context_root")
.short('C')
.long("context_root")
.num_args(0..1)
.number_of_values(1)
.value_name("CONTEXT_DIRECTORY")
.help("Set the directory to use as the root dir for context function definitions"),
)
.arg(
Arg::new("lib")
.short('l')
.long("lib")
.action(clap::ArgAction::SetTrue)
.help("Compile a flow library"),
)
.arg(
Arg::new("native")
.short('n')
.long("native")
.action(clap::ArgAction::SetTrue)
.help("Compile only native (not wasm) implementations when compiling a library")
.requires("lib"),
)
.arg(
Arg::new("lib_dir")
.short('L')
.long("libdir")
.num_args(0..)
.number_of_values(1)
.value_name("LIB_DIR|BASE_URL")
.help("Add a directory or base Url to the Library Search path"),
)
.arg(
Arg::new("graphs")
.short('g')
.long("graphs")
.action(clap::ArgAction::SetTrue)
.help("Create .dot files for graphs then generate SVGs with 'dot' command (if available)"),
)
.arg(
Arg::new("metrics")
.short('m')
.long("metrics")
.conflicts_with("compile")
.action(clap::ArgAction::SetTrue)
.help("Show flow execution metrics when execution ends"),
)
.arg(
Arg::new("wasm")
.short('w')
.long("wasm")
.action(clap::ArgAction::SetTrue)
.conflicts_with("compile")
.help("Use wasm library implementations when executing flow"),
)
.arg(
Arg::new("optimize")
.short('O')
.long("optimize")
.action(clap::ArgAction::SetTrue)
.help("Optimize generated output (flows and wasm)"),
)
.arg(
Arg::new("provided")
.short('p')
.long("provided")
.action(clap::ArgAction::SetTrue)
.help("Provided function implementations should NOT be compiled from source"),
)
.arg(
Arg::new("output")
.short('o')
.long("output")
.num_args(0..1)
.number_of_values(1)
.value_name("OUTPUT_DIR")
.help("Specify the output directory for generated manifest"),
)
.arg(
Arg::new("verbosity")
.short('v')
.long("verbosity")
.num_args(0..1)
.number_of_values(1)
.value_name("VERBOSITY_LEVEL")
.help("Set verbosity level for output (trace, debug, info, warn, error (default))"),
)
.arg(
Arg::new("stdin")
.short('i')
.long("stdin")
.num_args(0..1)
.number_of_values(1)
.value_name("STDIN_FILENAME")
.help("Read STDIN from the named file"),
)
.arg(
Arg::new("source_url")
.num_args(1)
.help("path or url for the flow or library to compile")
)
.arg(
Arg::new("flow_args")
.num_args(0..)
.trailing_var_arg(true)
.help("List of arguments get passed to the flow when executed")
);
app.get_matches()
}
fn parse_args(matches: ArgMatches) -> Result<Options> {
let verbosity = matches.get_one::<String>("verbosity").map(|s| s.as_str());
SimpleLogger::init_prefix(verbosity, false);
debug!(
"'{}' version {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
);
debug!("'flowclib' version {}", info::version());
let cwd = env::current_dir().chain_err(|| "Could not get current working directory value")?;
let cwd_url = Url::from_directory_path(cwd)
.map_err(|_| "Could not form a Url for the current working directory")?;
let url = url_from_string(&cwd_url,
matches.get_one::<String>("source_url")
.map(|s| s.as_str()))
.chain_err(|| "Could not create a url for flow from the 'FLOW' command line parameter")?;
let output_dir = source_arg::get_output_dir(&url,
matches.get_one::<String>("output")
.map(|s| s.as_str()))
.chain_err(|| "Could not get or create the output directory")?;
let lib_dirs = if matches.contains_id("lib_dir") {
matches
.get_many::<String>("lib_dir")
.chain_err(|| "Could not get the list of 'LIB_DIR' options specified")?
.map(|s| s.to_string())
.collect()
} else {
vec![]
};
let context_root = if matches.contains_id("context_root") {
let root_string = matches.get_one::<String>("context_root")
.ok_or("Could not get the 'CONTEXT_DIRECTORY' option specified")?;
let root = PathBuf::from(root_string);
Some(root.canonicalize()?)
} else {
None
};
let flow_args = match matches.get_many::<String>("flow_args") {
Some(strings) => strings.map(|s| s.to_string()).collect(),
None => vec![]
};
Ok(Options {
lib: matches.get_flag("lib"),
source_url: url,
flow_args,
graphs: matches.get_flag("graphs"),
wasm_execution: matches.get_flag("wasm"),
execution_metrics: matches.get_flag("metrics"),
compile_only: matches.get_flag("compile"),
debug_symbols: matches.get_flag("debug"),
provided_implementations: matches.get_flag("provided"),
output_dir,
stdin_file: matches.get_one::<String>("stdin").map(|s| s.to_string()),
lib_dirs,
native_only: matches.get_flag("native"),
context_root,
verbosity: verbosity.map(|s| s.to_string()),
optimize: matches.get_flag("optimize")
})
}