Skip to content

Commit

Permalink
Merge 5ddc3f0 into 2670528
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiburt committed Sep 13, 2021
2 parents 2670528 + 5ddc3f0 commit 3663107
Show file tree
Hide file tree
Showing 20 changed files with 1,469 additions and 462 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
@@ -1,6 +1,6 @@
name: Build

on: [push, pull_request]
on: [push]

env:
CARGO_TERM_COLOR: always
Expand All @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
rust: [nightly, stable]
platform: [ubuntu-latest, macos-latest]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/windows.yml
@@ -0,0 +1,26 @@
name: Build

on:
push:
# Sequence of patterns matched against refs/tags
tags:
- win # Push events to win tag
- win.* # Push events to win.0, win.1, and win.9 tags

env:
CARGO_TERM_COLOR: always

jobs:
test:
name: Windows Test Suite
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/cargo@v1
with:
command: build
- uses: actions-rs/cargo@v1
timeout-minutes: 3
with:
command: test
args: --verbose --no-fail-fast
11 changes: 8 additions & 3 deletions Cargo.toml
Expand Up @@ -13,12 +13,17 @@ keywords = ["pty", "automation", "testing", "expect", "rexpect"]
readme = "README.md"

[features]
async = ["futures-lite", "async-io"]
log = []
async = ["futures-lite", "async-io"]

[dependencies]
ptyprocess = { git = "https://github.com/zhiburt/ptyprocess" }
nix = "0.21.0"
regex = "1.5.4"
futures-lite = { version = "1.12.0", optional = true }
async-io = { version="1.6.0", optional=true }

[target.'cfg(windows)'.dependencies]
conpty = "0.2.1"

[target.'cfg(unix)'.dependencies]
ptyprocess = { git = "https://github.com/zhiburt/ptyprocess" }
nix = "0.21.0"
24 changes: 16 additions & 8 deletions examples/bash.rs
@@ -1,7 +1,9 @@
// An example is based on README.md from https://github.com/philippkeller/rexpect

#[cfg(unix)]
use expectrl::{repl::spawn_bash, ControlCode, Regex};

#[cfg(unix)]
#[cfg(not(feature = "async"))]
fn main() {
let mut p = spawn_bash().unwrap();
Expand All @@ -22,9 +24,9 @@ fn main() {
p.expect_prompt().unwrap(); // go sure `wc` is really done
println!(
"/etc/passwd has {} lines, {} words, {} chars",
String::from_utf8_lossy(lines.found_match()),
String::from_utf8_lossy(words.found_match()),
String::from_utf8_lossy(bytes.found_match()),
String::from_utf8_lossy(lines.first()),
String::from_utf8_lossy(words.first()),
String::from_utf8_lossy(bytes.first()),
);

// case 3: read while program is still executing
Expand All @@ -34,13 +36,14 @@ fn main() {
let duration = p.expect(Regex("[0-9. ]+ ms")).unwrap();
println!(
"Roundtrip time: {}",
String::from_utf8_lossy(duration.found_match())
String::from_utf8_lossy(duration.first())
);
}

p.send_control(ControlCode::EOT).unwrap();
}

#[cfg(unix)]
#[cfg(feature = "async")]
fn main() {
use futures_lite::io::AsyncBufReadExt;
Expand All @@ -64,9 +67,9 @@ fn main() {
p.expect_prompt().await.unwrap(); // go sure `wc` is really done
println!(
"/etc/passwd has {} lines, {} words, {} chars",
String::from_utf8_lossy(lines.found_match()),
String::from_utf8_lossy(words.found_match()),
String::from_utf8_lossy(bytes.found_match()),
String::from_utf8_lossy(lines.first()),
String::from_utf8_lossy(words.first()),
String::from_utf8_lossy(bytes.first()),
);

// case 3: read while program is still executing
Expand All @@ -76,10 +79,15 @@ fn main() {
let duration = p.expect(Regex("[0-9. ]+ ms")).await.unwrap();
println!(
"Roundtrip time: {}",
String::from_utf8_lossy(duration.found_match())
String::from_utf8_lossy(duration.first())
);
}

p.send_control(ControlCode::EOT).await.unwrap();
})
}

#[cfg(windows)]
fn main() {
panic!("An example doesn't supported on windows")
}
8 changes: 8 additions & 0 deletions examples/bash_control.rs
@@ -1,7 +1,9 @@
// An example is based on README.md from https://github.com/philippkeller/rexpect

#[cfg(unix)]
use expectrl::{repl::spawn_bash, ControlCode, Error};

#[cfg(unix)]
#[cfg(not(feature = "async"))]
fn main() -> Result<(), Error> {
let mut p = spawn_bash()?;
Expand All @@ -24,6 +26,7 @@ fn main() -> Result<(), Error> {
Ok(())
}

#[cfg(unix)]
#[cfg(feature = "async")]
fn main() -> Result<(), Error> {
futures_lite::future::block_on(async {
Expand All @@ -46,3 +49,8 @@ fn main() -> Result<(), Error> {
Ok(())
})
}

#[cfg(windows)]
fn main() {
panic!("An example doesn't supported on windows")
}
14 changes: 6 additions & 8 deletions examples/expect_line.rs
@@ -1,5 +1,3 @@
// An example is based on README.md from https://github.com/philippkeller/rexpect

use expectrl::{self, Any, Eof};

#[cfg(not(feature = "async"))]
Expand All @@ -11,16 +9,16 @@ fn main() {
.expect(Any(vec![Box::new("\r"), Box::new("\n"), Box::new(Eof)]))
.expect("Expect failed");

let is_eof = m.found_match().is_empty();
let is_eof = m.first().is_empty();
if is_eof {
break;
}

if m.found_match() == [b'\n'] {
if m.first() == [b'\n'] {
continue;
}

println!("{:?}", String::from_utf8_lossy(m.before_match()));
println!("{:?}", String::from_utf8_lossy(m.first()));
}
}

Expand All @@ -35,16 +33,16 @@ fn main() {
.await
.expect("Expect failed");

let is_eof = m.found_match().is_empty();
let is_eof = m.first().is_empty();
if is_eof {
break;
}

if m.found_match() == [b'\n'] {
if m.first() == [b'\n'] {
continue;
}

println!("{:?}", String::from_utf8_lossy(m.before_match()));
println!("{:?}", String::from_utf8_lossy(m.first()));
}
})
}
16 changes: 16 additions & 0 deletions examples/interact.rs
@@ -1,7 +1,10 @@
/// To run an example run the following command
/// `cargo run --example interact`.

#[cfg(unix)]
use expectrl::repl::spawn_bash;

#[cfg(unix)]
#[cfg(not(feature = "async"))]
fn main() {
let mut bash = spawn_bash().expect("Error while spawning bash");
Expand All @@ -14,6 +17,7 @@ fn main() {
println!("Quiting status {:?}", status);
}

#[cfg(unix)]
#[cfg(feature = "async")]
fn main() {
let mut bash = futures_lite::future::block_on(spawn_bash()).expect("Error while spawning bash");
Expand All @@ -25,3 +29,15 @@ fn main() {

println!("Quiting status {:?}", status);
}

#[cfg(windows)]
fn main() {
let mut pwsh = expectrl::spawn("cmd").expect("Error while spawning bash");

println!("Now you're in interacting mode");
println!("To return control back to main type CTRL-]");

pwsh.interact().expect("Failed to start interact");

println!("Quiting");
}
46 changes: 46 additions & 0 deletions examples/powershell.rs
@@ -0,0 +1,46 @@
#[cfg(windows)]
fn main() {
use expectrl::{repl::spawn_powershell, ControlCode, Regex};

let mut p = spawn_powershell().unwrap();

// case 1: execute
let hostname = p.execute("hostname").unwrap();
println!(
"Current hostname: {:?}",
String::from_utf8(hostname).unwrap()
);

// case 2: wait until done, only extract a few infos
p.send_line("type README.md | Measure-Object -line -word -character")
.unwrap();
let lines = p.expect(Regex("[0-9]+\\s")).unwrap();
let words = p.expect(Regex("[0-9]+\\s")).unwrap();
let bytes = p.expect(Regex("([0-9]+)[^0-9]")).unwrap();
// go sure `wc` is really done
p.expect_prompt().unwrap();
println!(
"/etc/passwd has {} lines, {} words, {} chars",
String::from_utf8_lossy(lines.first()),
String::from_utf8_lossy(words.first()),
String::from_utf8_lossy(bytes.matches()[1]),
);

// case 3: read while program is still executing
p.send_line("ping 8.8.8.8 -t").unwrap();
for _ in 0..5 {
let duration = p.expect(Regex("[0-9.]+ms")).unwrap();
println!(
"Roundtrip time: {}",
String::from_utf8_lossy(duration.first())
);
}

p.send_control(ControlCode::ETX).unwrap();
p.expect_prompt().unwrap();
}

#[cfg(not(windows))]
fn main() {
panic!("An example doesn't supported on windows")
}
10 changes: 4 additions & 6 deletions examples/python.rs
Expand Up @@ -7,10 +7,9 @@ fn main() {
p.execute("import platform").unwrap();
p.send_line("platform.node()").unwrap();

// todo: add support for matches in 'Found' + iterator?
let found = p.expect(Regex(r"'\w+'")).unwrap();
let found = p.expect(Regex(r"'.*'")).unwrap();

println!("Platform {}", String::from_utf8_lossy(found.found_match()));
println!("Platform {}", String::from_utf8_lossy(found.first()));
}

#[cfg(feature = "async")]
Expand All @@ -21,9 +20,8 @@ fn main() {
p.execute("import platform").await.unwrap();
p.send_line("platform.node()").await.unwrap();

// todo: add support for matches in 'Found' + iterator?
let found = p.expect(Regex(r"'\w+'")).await.unwrap();
let found = p.expect(Regex(r"'.*'")).await.unwrap();

println!("Platform {}", String::from_utf8_lossy(found.found_match()));
println!("Platform {}", String::from_utf8_lossy(found.first()));
})
}
14 changes: 14 additions & 0 deletions src/error.rs
Expand Up @@ -6,7 +6,10 @@ use std::io;
#[derive(Debug)]
pub enum Error {
IO(io::Error),
#[cfg(unix)]
Nix(ptyprocess::Error),
#[cfg(windows)]
Win(conpty::Error),
CommandParsing,
RegexParsing,
ExpectTimeout,
Expand All @@ -18,7 +21,10 @@ impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::IO(err) => write!(f, "IO error {}", err),
#[cfg(unix)]
Error::Nix(err) => write!(f, "Nix error {}", err),
#[cfg(windows)]
Error::Win(err) => write!(f, "Win error {}", err),
Error::CommandParsing => write!(f, "Can't parse a command string, please check it out"),
Error::RegexParsing => write!(f, "Can't parse a regex expression"),
Error::ExpectTimeout => write!(f, "Reached a timeout for expect type of command"),
Expand All @@ -36,12 +42,20 @@ impl From<io::Error> for Error {
}
}

#[cfg(unix)]
impl From<ptyprocess::Error> for Error {
fn from(err: ptyprocess::Error) -> Self {
Self::Nix(err)
}
}

#[cfg(windows)]
impl From<conpty::Error> for Error {
fn from(err: conpty::Error) -> Self {
Self::Win(err)
}
}

impl From<String> for Error {
fn from(message: String) -> Self {
Self::Other(message)
Expand Down

0 comments on commit 3663107

Please sign in to comment.