// This is an example step library.

// Our step function uses the subplotlib Runcmd context
// in order that it can access the stdout of the recently
// executed command.
use subplotlib::steplibrary::runcmd::Runcmd;

// All step functions are annotated with the #[step] attribute
// which is implemented in the subplotlib-derive crate.
// This marks the function as a step function and automatically
// creates the step builders and other code needed to be able to
// use this step in a scenario.
#[step]
// The first argument to a step function is always the context with
// which the step is run.  If the step is simple (needs only one
// context) then this will be a borrow (or mut borrow) of the context
// type.  If the step uses multiple kinds of context then instead there
// will be some number of #[context(TypeName)] attributes, and the step
// function will take a ScenarioContext as its context argument.
// The rest of the arguments to the step function are the captures from the
// binding for the step.
fn count_lines_in_stdout(context: &Runcmd, count: usize) {
    let stdout_count = context.stdout_as_string().lines().count();
    if stdout_count != count {
        // Step functions can use the throw!() macro to raise a
        // step error.  This will be reported as the reason the
        // scenario fails.
        throw!(format!(
            "Incorrect number of lines, got {stdout_count} expected {count}",
        ));
    }
}

// While this step function could just use Runcmd again, we use ScenarioContext
// in order to demonstrate how to access other contexts from a generic step
// function in Rust

#[step]
#[context(Runcmd)]
fn stderr_contains_two_things(context: &ScenarioContext, what: &str, other: &str) {
    // To keep lifetimes sane, you access other contexts by using closures which
    // take the context as an argument.
    let stderr_has_both = context.with(
        |runcmd: &Runcmd| {
            // In here, `runcmd` is the context we need
            let stderr = runcmd.stderr_as_string();
            // Since we could fail in here, we're actually returning a Result object
            Ok(stderr.contains(what) && stderr.contains(other))
        },
        // This `false` means that we will not run the closure if the scenario is
        // currently failing.  This is more useful in cleanup functions since it
        // allows you to decide if you want to ignore the failure mode in order to
        // clean up more effectively.
        false,
    )?;

    if !stderr_has_both {
        throw!(format!(
            "Stderr does not contain both of {what:?} and {other:?}",
        ))
    }
}
