willdo 0.0.1

Task manager with DAG
Documentation
//! convenience building blocks for cli apps

use crate::config::ConfigurationEntry;
use crate::execution::{progress::Progress, repository::JobRepository, Runner as _, Simple};
use crate::graph::{Builder, Graph};
use crate::job::JobId;
use futures_lite::{Stream, StreamExt};
use std::env;

/// You can use this as a main entry point without your own async runtime.
/// It configures the WillDo from environment variables and falls back on reasonable defaults.
#[cfg(feature = "main-blocking")]
pub fn main() -> Result<(), Error> {
    futures_lite::future::block_on(main_async())
}
/// You can use this as a main entry point without your own async runtime.
/// It configures the WillDo from environment variables and falls back on reasonable defaults.
#[cfg(not(feature = "main-blocking"))]
pub fn main() -> Result<(), Error> {
    tree()?;
    eprintln!("The main-blocking feature is not enabled, cannot run jobs, then.");
    std::process::exit(1)
}

/// You can use this as a main entry point or a sub task.
/// It configures the WillDo from environment variables and falls back on reasonable defaults.
pub async fn main_async() -> Result<(), Error> {
    logging();
    let mut graph = build()?;
    {
        let mut events = run(&mut graph);
        while let Some((id, outcome)) = events.next().await {
            println!("{id}: {outcome:?}");
        }
    }
    log::info!("Graph:\n{graph}");
    Ok(())
}
/// print a tree of required jobs
pub fn tree() -> Result<(), Error> {
    logging();    
    let goals = goals();
    let graph = build()?;
    for (indent, job, conditions) in graph.tree(&goals) {
        print!("{}", "  ".repeat(indent));
        println!("{job} ({conditions})");
    }
    log::info!("Graph:\n{graph}");
    Ok(())
}

/// initialize available logger
pub fn logging() {
    #[cfg(feature = "pretty_env_logger")]
    return pretty_env_logger::init();
    #[cfg(feature = "env_logger")]
    return env_logger::init();
}
/// get goals from environment
pub fn goals() -> Vec<Box<str>> {
    let goals = env::var("WILLDO_GOALS")
        .unwrap_or_default()
        .split(",")
        .map(Into::<Box<str>>::into)
        .filter(|goal| !goal.is_empty())
        .collect::<Vec<Box<str>>>();
    goals
}
/// get default configuration
pub fn config(
) -> impl IntoIterator<Item = Result<impl ConfigurationEntry, crate::config::LoadError>> {
    crate::config::load(env::var("WILLDO_CONFIG").unwrap_or(".".into()))
}
/// create and configure the builder, in time to add providers before build
pub fn builder() -> Result<Builder, Error> {
    let config = config();
    let mut builder = Graph::builder();
    for entry in config {
        entry?.configure(&mut builder)?;
    }
    Ok(builder)
}
/// build the graph with defaults
pub fn build() -> Result<Graph, Error> {
    let mut builder = builder()?;

    #[cfg(feature = "subprocess")]
    builder.configure_provider(
        "subprocess",
        crate::execution::commander::subprocess::provide,
    );

    let graph = builder.build()?;
    Ok(graph)
}
/// run a job graph with defaults
pub fn run<'j, J: 'j + JobRepository + Send + Unpin>(
    jobs: J,
) -> impl 'j + Stream<Item = (JobId, Progress)> {
    let goals = goals();
    Simple.run(jobs, &goals)
}

/// Combined willdo error
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// Error encountered during config loading
    #[error("Configuration failed - {0}")]
    Configuration(#[from] crate::config::LoadError),
    /// Error encountered during job graph building
    #[error("Task graph build failed - {0}")]
    Graph(#[from] crate::graph::BuildError),
}