expectrl

repository·main·Indexed 42 days ago

https://github.com/zhiburt/expectrl

A Rust library for automating interactive terminal applications in Unix-like environments, similar to the classic Expect tool. It allows developers to spawn child processes and control them by responding to expected output patterns using strings or regex. Key features include synchronous and asynchronous support, non-blocking pattern checks via `check` and `is_matched`, and `InteractSession` for complex stateful interactions.

Tokens
10.1K
Snippets
37
Records
49
Agent score
52%

What's inside expectrl

  1. Review expectrl features and capabilities

    main

    Expectrl provides the following core features:

    • Async Support: Available via the async feature flag.
    • Logging: Built-in support for logging interactions.
    • Interaction: Supports the interact function/session for complex logic.
    • Cross-platform: Works on Windows.
  2. Use InteractSession for complex user interaction

    main

    For scenarios requiring complex logic (like reading passwords from stdin or reacting to specific output patterns dynamically), use InteractSession.

    InteractSession allows you to define an output_action closure that inspects the process buffer (ctx.buf) and determines how the session should proceed. This is useful for stateful interactions where the next step depends on the output received.

    use std::io::stdout;
    use expectrl::{
        interact::{actions::lookup::Lookup, InteractSession},
        stream::stdin::Stdin,
        ControlCode, Error, Expect, Regex,
    };
    
    fn main() -> Result<(), Error> {
        let mut p = expectrl::spawn("ftp bks4-speedtest-1.tele2.net")?;
    
        let mut search = Lookup::new();
        let mut stdin = Stdin::open()?;
    
        let authenticated = {
            let mut session = InteractSession::new(&mut p, &mut stdin, stdout(), false);
            session.set_output_action(move |ctx| {
                let found = search.on(ctx.buf, ctx.eof, "Login successful")?;
                if found.is_some() {
                    *ctx.state = true;
                    return Ok(true);
                }
    
                Ok(false)
            });
    
            session.spawn()?;
    
            session.into_state()
        };
    
        stdin.close()?;
    
        if !authenticated {
            println!("An authentication was not passed");
            return Ok(());
        }
    
        p.expect("ftp>")?;
        p.send_line("cd upload")?;
        p.expect("successfully changed.")?;
        p.send_line("pwd")?;
        p.expect(Regex("[0-9]+ \"/upload\""))?;
        p.send(ControlCode::EndOfTransmission)?;
        p.expect("Goodbye.")?;
    
        Ok(())
    }
  3. Automate interactive processes with expectrl

    main

    You can automate terminal applications by spawning a process and using .expect() to wait for specific patterns (strings or regex) and .send_line() to provide input. This mimics a human interacting with a CLI.

    Key methods:

    • expectrl::spawn(cmd): Spawns the child process.
    • p.expect(pattern): Waits for a pattern. Patterns can be a string literal, a Regex, or the Eof constant.
    • p.send_line(text): Sends a line of text followed by a newline.
    • p.send(ControlCode): Sends specific control codes.
    use expectrl::{Regex, Eof, Error, Expect};
    
    fn main() -> Result<(), Error> {
        let mut p = expectrl::spawn("ftp speedtest.tele2.net")?;
        p.expect(Regex("Name \(.*\):"))?;
        p.send_line("anonymous")?;
        p.expect("Password")?;
        p.send_line("test")?;
        p.expect("ftp>")?;
        p.send_line("cd upload")?;
        p.expect("successfully changed.\r\nftp>")?;
        p.send_line("pwd")?;
        p.expect(Regex("[0-9]+ \"/upload\""))?;
        p.send_line("exit")?;
        p.expect(Eof)?;
    
        Ok(())
    }
  4. Install expectrl via Cargo

    main

    To use expectrl in your Rust project, add it to your Cargo.toml dependencies.

    Note: The README example uses version 0.8, though the current version is 0.6.0. Always check crates.io for the latest version.

    # Cargo.toml
    [dependencies]
    expectrl = "0.8"
  5. Automate terminal applications with expectrl

    main

    expectrl is a tool for automating interactive terminal applications. It allows you to spawn processes, control them, and interact with their standard input/output (IO). It is specifically designed for interactive applications where you need to 'expect' certain output patterns before sending input.

    Key Features

    • Spawn and Control: Easily start processes and manage their lifecycle.
    • Pattern Matching: Use strings or regular expressions to wait for specific output.
    • Async Support: Use the async feature flag to enable async/await APIs.
    • Logging: Wrap sessions to log interactions to a stream (e.g., stdout).
  6. How Any works for multiple pattern matching

    main

    The Any<I> struct allows you to combine multiple Needle implementations into a single search operation. It checks the provided needles in the order they are provided and returns the first successful match it finds.

    Using fixed-size arrays or slices

    You can pass arrays or slices of types that implement Needle directly to Any:

    // Using an array of strings
    let needle = Any(["we", "are", "here"]);

    Using Any::boxed for heterogeneous needles

    If you need to combine different types of needles (e.g., a String and an NBytes), you must use Any::boxed with a Vec<Box<dyn Needle>>:

    use expectrl::{Any, NBytes};
    
    let needle = Any::boxed(vec![
        Box::new("we"), 
        Box::new(NBytes(3))
    ]);
    use expectrl::{spawn, Any};
    
    let mut p = spawn("cat").unwrap();
    p.expect(Any(["we", "are", "here"])).unwrap();
    
    // For mixed types:
    let mut p = spawn("cat").unwrap();
    p.expect(Any::boxed(vec![Box::new("we"), Box::new(NBytes(3))])).unwrap();
  7. Use the ReplSession abstraction

    main

    A ReplSession<S> is a wrapper around a session S that manages a Read-Eval-Print Loop (REPL). It is designed for shells where a specific prompt identifies that a command has finished executing.

    Key capabilities include:

    • Prompt Management: Automatically waits for a specific prompt string using expect_prompt().
    • Command Execution: Send commands and retrieve their output using execute(cmd).
    • Lifecycle Control: Define a set_quit_command(cmd) that is sent when exit() is called.
    • Echo Control: Use set_echo(bool) to determine if the session should verify that sent input is echoed back by the shell.
    // Example workflow with a ReplSession
    let mut session = spawn_bash()?;
    session.expect_prompt()?; // Wait for initial prompt
    
    // Execute a command and get output
    let output = session.execute("ls -la")?;
    
    // Exit gracefully
    session.exit()?;
  8. How `expect`, `check`, and `is_matched` differ

    main

    When interacting with a process stream, choosing the right matching method is critical for both performance and correctness:

    MethodBlocking?Consumes Bytes?Strategy
    expectYes (waits for match/timeout)YesUses gready (default) or lazy matching.
    checkNoYesSearches against all currently available bytes.
    is_matchedNoNoSearches against all currently available bytes; returns bool.

    When to use what:

    • Use expect when your logic depends on a specific event occurring (e.g., waiting for a prompt).
    • Use check when you want to see if something has already happened without stopping your program.
    • Use is_matched for simple boolean checks where you don't need to capture the matched content.
  9. Enable async and polling features

    main

    To use specific advanced capabilities, you must enable the corresponding feature flags in your Cargo.toml:

    • async: Enables the async/await public API.
    • polling: Enables the polling backend in interact sessions. Note: Use caution when using this on Windows.
  10. Spawn a pre-configured Python REPL session

    main

    Use spawn_python() to create a ReplSession wrapping a python process. It uses the default Python IDLE prompt (>>> ) and sets the quit command to quit().

    Note: This function is currently not supported on Windows.

    // Synchronous usage
    let mut python = spawn_python()?;
    
    // Async usage (requires 'async' feature)
    let mut python = spawn_python().await?;
  11. Use the Captures struct to access matched patterns

    main

    The Captures struct represents the result of a pattern match within a buffer. It allows you to retrieve the specific byte slices that matched a pattern, the data preceding the first match, or iterate through all matches found.

    Key methods:

    • is_empty(): Returns true if no matches were found.
    • get(index: usize): Returns an Option<&[u8]> containing the bytes of the match at the specified index. Returns None if the index is out of bounds.
    • before(): Returns a slice of the bytes appearing before the very first match.
    • as_bytes(): Returns the entire underlying buffer.
    • matches(): Returns a MatchIter to iterate over all matched byte slices.
    • Index<usize>: You can use standard indexing (e.g., &captures[0]) to access matches, but this will panic if the index is out of bounds or if no matches exist.
    // Assuming Captures is provided by the library
    let captures = Captures::new(b"You can use iterator".to_vec(), vec![Match::new(0, 3), Match::new(4, 7)]);
    
    // Access specific matches safely
    if let Some(first_match) = captures.get(0) {
        println!("Found: {:?}", first_match); // b"You"
    }
    
    // Access data before the first match
    let prefix = captures.before(); 
    
    // Access all bytes
    let all_bytes = captures.as_bytes();
  12. Configure non-blocking IO with the NonBlocking trait

    main

    The NonBlocking trait can be implemented for any std::io::Reader to allow switching between blocking and non-blocking modes. This is useful for performing read operations that return immediately rather than waiting for data.

    /// Sets a [std::io::Read]er into a non/blocking mode.
    fn set_blocking(&mut self, on: bool) -> Result<()>;