Coder Social home page Coder Social logo

zhiburt / expectrl Goto Github PK

View Code? Open in Web Editor NEW
162.0 4.0 13.0 715 KB

A rust library for controlling interactive programs in a pseudo-terminal

License: MIT License

Rust 99.22% Python 0.78%
pty tty rust unix process processes expect rexpect pexpect console

expectrl's Introduction

Build coverage status crate docs.rs

expectrl

Expectrl is a tool for automating terminal applications.

Expectrl is a rust module for spawning child applications and controlling them and responding to expected patterns in process's output. Expectrl works like Don Libes' Expect. Expectrl allows your script to spawn a child application and control it as if a human were typing commands.

Using the library you can:

  • Spawn process
  • Control process
  • Interact with process's IO(input/output).

expectrl like original expect may shine when you're working with interactive applications. If your application is not interactive you may not find the library the best choise.

Usage

Add expectrl to your Cargo.toml.

# Cargo.toml
[dependencies]
expectrl = "0.7"

An example where the program simulates a used interacting with ftp.

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(())
}

The same example but the password will be read from stdin.

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 auth = false;
    let mut login_lookup = Lookup::new();
    let mut stdin = Stdin::open()?;

    InteractSession::new(&mut p, &mut stdin, stdout(), &mut auth)
        .set_output_action(move |ctx| {
            if login_lookup
                .on(ctx.buf, ctx.eof, "Login successful")?
                .is_some()
            {
                **ctx.state = true;
                return Ok(true);
            }

            Ok(false)
        })
        .spawn()?;

    stdin.close()?;

    if !auth {
        println!("An authefication 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(())
}

Features

  • It has an async support (To enable them you must turn on an async feature).
  • It supports logging.
  • It supports interact function.
  • It works on windows.

Notes

It was originally inspired by philippkeller/rexpect and pexpect.

Licensed under MIT License

expectrl's People

Contributors

james-chf avatar zhiburt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

expectrl's Issues

Session::spawn() vs expectrl::spawn() issues with std::process::Command

expectrl::Session::spawn() accepts a std::process::Command, allowing some options like which directory to run in. Is it compatible with interact()? Or should expectrl::spawn also have a version that accepts a Command?

This code works as expected, printing out the help for git diff, then exiting:

let mut cmd = std::process::Command::new("git");
cmd.args(["diff"]);
cmd.current_dir("/tmp");
let mut session = expectrl::Session::spawn(cmd).expect("Can't spawn a session");
let mut opts = expectrl::interact::InteractOptions::terminal().unwrap();
let res = opts.interact(&mut session).unwrap();

But when a Command is used with the code in this comment of issue 13, Some unexpected behavior occurs.

Specifically, (1) replacing

let mut session = expectrl::spawn("./terminal_example.py").expect("Can't spawn a session");

with

let mut cmd = std::process::Command::new("git");
let mut session = expectrl::Session::spawn(cmd).expect("Can't spawn a session");

causes the output to stop partway through printing the git help.

(2) Replacing the spawn line with

let mut cmd = std::process::Command::new("git");
cmd.args(["diff"]);
let mut session = expectrl::Session::spawn(cmd).expect("Can't spawn a session");

works as expected.

(3) Replacing the spawn line with

let mut cmd = std::process::Command::new("git");
cmd.args(["diff"]);
cmd.current_dir("/tmp");
let mut session = expectrl::Session::spawn(cmd).expect("Can't spawn a session");

also causes the output to stop partway through printing the git help.

There could be several issues here:

  1. It's notable that the simple example above works as expected, but the demo code doesn't. Is there something about using on_output or on_idle that causes the problem?
  2. How do we use a spawn that accepts a Command and works with interact()?
  3. Which spawn is the right one to use with interact()? It seems confusing to have multiple functions with similar names in different namespaces, eg Session::spawn() vs expectrl::spawn().

Can't spawn() after interact()

Running another spawn() after calling interact() causes the spawn to fail with a Sys(EBADF) error.

If the repro code below is run without the interact() function, it calls spawn() and prints the process info as expected:

Running PtyProcess { master: Master { fd: PtyMaster(7) }, child_pid: Pid(97435), stream: 
Stream { inner: File { fd: 9, path: "/dev/ptmx", read: true, write: true }, reader: BufReader { 
reader: Reader { inner: File { fd: 10, path: "/dev/ptmx", read: true, write: true } }, buffer: 0/8192 
} }, eof_char: 4, intr_char: 3, terminate_approach_delay: 100ms }

However, if the interact() function is called, the subsequent call to spawn() fails:

Cargo.lock	Cargo.toml	src		target
Quiting status Exited(Pid(96414), 0)
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Sys(EBADF)', src/main.rs:20:42
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Repro code:

use expectrl::Session;
use ptyprocess::PtyProcess;
use std::{process::Command, thread, time::Duration};

fn main() {
    let cmd = Command::new("ls");
    let mut bash = Session::spawn_cmd(cmd).expect("Error while spawning bash");
    //
    // If these two lines are commented out, the spawn() below starts and the
    // process information is printed.
    //
    // If these two lines are enabled, the interact() succeeds and the exit code
    // 0 is printed, but the spawn() below panics.
    let status = bash.interact().expect("Failed to start interact");
    println!("Quiting status {:?}", status);

    // Now start another.
    thread::sleep(Duration::from_millis(500));   // Delay just in case.

    let cmd = Command::new("ls");
    let process = PtyProcess::spawn(cmd).unwrap();
    println!("Running {:?}", process);
}

Tested on macOs.

sleep() before interactive() causes freeze

The code below run as expected, printing the git diff help, then result StillAlive, then exiting.

fn main() {
    let mut cmd = std::process::Command::new("git");
    cmd.args(["diff"]);
    cmd.current_dir("/tmp");
    let mut session = expectrl::Session::spawn(cmd).expect("Can't spawn a session");
    let mut opts = expectrl::interact::InteractOptions::terminal().unwrap();
    let res = opts.interact(&mut session).unwrap();
    println!("result {:?}", res);
}

This code freezes, printing nothing and not exiting:

fn main() {
    let mut cmd = std::process::Command::new("git");
    cmd.args(["diff"]);
    cmd.current_dir("/tmp");
    let mut session = expectrl::Session::spawn(cmd).expect("Can't spawn a session");
    std::thread::sleep(std::time::Duration::from_millis(300));
    let mut opts = expectrl::interact::InteractOptions::terminal().unwrap();
    let res = opts.interact(&mut session).unwrap();
    println!("result {:?}", res);
}

Hitting return causes the program to continue as before. It seems like the delay is causing expectrl to enter the interactive mode in a blocking way, but that's not quite right because interactive mode should require ^] to exit, not just a return. Also, there's no input command here awaiting a return.

The expected behavior would be that the output is exactly as before but delayed 300ms.

spawn_bash execute

let mut p = spawn_bash().unwrap();
let hostname = p.execute("ps aux").unwrap();

output:
dpg 1172 0.0 0.0 154828 1448 ? Ss 08:27 0:00 postgres: logge
dpg 1174 0.0 0.2 302672 4752 ? Ss 08:27 0:00 postgres: check
dpg 1175 0.0 0.2 302572 4380 ? Ss 08:27 0:00 postgres: backg
dpg 1176 0.0 0.2 302572 5696 ? Ss 08:27 0:05 postgres: walwr
dpg 1177 0.0 0.1 303236 2692 ? Ss 08:27 0:00 postgres: autov
dpg 1178 0.0 0.0 157076 1732 ? Ss 08:27 0:01 postgres: stats

but in putty
dpg 1172 0.0 0.0 154828 1448 ? Ss 08:27 0:00 postgres: logger
dpg 1174 0.0 0.2 302672 4752 ? Ss 08:27 0:00 postgres: checkpointer
dpg 1175 0.0 0.2 302572 4644 ? Ss 08:27 0:00 postgres: background writer
dpg 1176 0.0 0.2 302572 5696 ? Ss 08:27 0:06 postgres: walwriter
dpg 1177 0.0 0.1 303236 2696 ? Ss 08:27 0:01 postgres: autovacuum launcher
dpg 1178 0.0 0.0 157076 1732 ? Ss 08:27 0:02 postgres: stats collector
dpg 1179 0.0 0.1 303108 2240 ? Ss 08:27 0:00 postgres: logical replication launcher

Capture waidpid result in interact()

Today when using interact the exit code is lost because is_alive will swallow it. It would be nice if the session could hold on to the status() so that it can be called again later to retrieve the first observed status.

Command arguments are not respected on Windows

This will ignore the args in the std::process::Command:

let mut cmd = std::process::Command::new("some_command");
cmd.env("some", "value").args(&["arg1"]);
let p = expectrl::Session::spawn(cmd).unwrap();

This seems to be because conpty 0.3 does not do anything with the arguments (maybe I'm misreading the code though):

https://github.com/zhiburt/conpty/blob/de641e6cf33ad5176e6090318c4723716bdc964c/src/lib.rs#L270
https://github.com/zhiburt/conpty/blob/de641e6cf33ad5176e6090318c4723716bdc964c/src/lib.rs#L398C47-L437

`interact()` seems consumes too much CPU

Example, following example will cause 100% CPU usage.

fn main() {
    let shell = "bash";
    let mut sh = expectrl::spawn(shell.to_string()).expect("Error while spawning sh");

    println!("Connecting to {}", shell);

    sh.interact().unwrap();
}

`check_eof` test fails on macOS

On the latest commit at the time of writing

expectrl/tests/check.rs

Lines 87 to 106 in cbd0114

#[cfg(unix)]
#[cfg(not(feature = "async"))]
#[test]
fn check_eof() {
let mut session = spawn("echo 'Hello World'").unwrap();
thread::sleep(Duration::from_millis(600));
let m = session.check(Eof).unwrap();
assert_eq!(m.before(), b"");
#[cfg(target_os = "linux")]
assert_eq!(m.get(0).unwrap(), b"'Hello World'\r\n");
#[cfg(not(target_os = "linux"))]
{
if m.matches().len() > 0 {
assert_eq!(m.get(0).unwrap(), b"'Hello World'\r\n");
}
}
}

To reproduce:

cargo test check_eof -- --exact

Test output on macOS 13 with zsh

running 1 test
test check_eof ... FAILED

failures:

---- check_eof stdout ----
thread 'check_eof' panicked at 'assertion failed: `(left == right)`
  left: `[]`,
 right: `[39, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 39, 13, 10]`', tests/check.rs:103:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    check_eof

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 9 filtered out; finished in 0.62s

error: test failed, to rerun pass '--test check'

`Interact::on_input` in async mode doesn't take Session as an argument

The issue is we can't Send+Clone &mut Self for future which will be spawned in handler.

We need to use some kind of mutex but we can't use the one in std because its blocking.
We could chose a tokio implemetnation or async-std or something else and provide an argument as Arc<Mutex<Session>>.

But in such case we bind the user with our chose which is sort of bad.

The possible solution is to ask user to provide an implementation of Mutex via given Fn() -> impl future::Future<Output=&mut Self> .
And I tried to follow this approach but... it didn't worked out.

So I open an issue as a reminder and just in case someone holds some thoughts.

issue #15

Reduce verbosity of getting a session with a logged stream

Just testing out this crate and it works great so far!

What I've found though, is sometimes I want to see the output of what's happening and so I use a session with a logged stream. The types I had to write are extremely verbose though:

#[cfg(unix)]
type PtyProcess = expectrl::process::unix::UnixProcess;
#[cfg(windows)]
type PtyProcess = expectrl::process::windows::WinProcess;

#[cfg(unix)]
type PtyProcessStream = expectrl::process::unix::PtyStream;
#[cfg(windows)]
type PtyProcessStream = expectrl::process::windows::ProcessStream;

type PtyLoggedStream =
  expectrl::stream::log::LoggedStream<PtyProcessStream, io::Stderr>;
type PtyLoggingSession = expectrl::Session<PtyProcess, PtyLoggedStream>;

I noticed some of these are defined in the crate, but they're not public. Perhaps they should be or perhaps there should be some easier way of doing this? I'm using this crate for testing, so I'm creating my own wrapper API on top of this where I can just set an option to get logging on, but it's quite verbose so far.

"Resource temporarily unavailable" in interact example

To reproduce:
run interact example
execute this in the spawned shell: python -c "print('\n'*10000000)" (or more if it doesn't error ;))

Error: thread 'main' panicked at 'Failed to start interact: IO(Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" })', examples/interact.rs:23:10

Full backtrace:
err.txt

Relevant setup info:
Linux 6.0.1, alacritty terminal

This doesn't happen in other programs that do similar things (poetry, qmk). There everything works as expected

PS: It also can happen in not-so-extreme scenarios, but that's a reliable way to reproduce this

Add `log` support for `repl`s

#24 removes this support its necessary to figure out the best way how to return this support back.

So code like this could be possible.

    let mut bash = expectrl::repl::spawn_bash().unwrap().with_log(Stdin).unwrap();

Test

#[test]
fn log_bash() {
    let writer = StubWriter::default();
    let mut bash = expectrl::repl::spawn_bash().unwrap().with_log(writer.clone()).unwrap();

    bash.send_line("echo Hello World").unwrap();

    let mut buf = String::new();
    let _ = bash.read_line(&mut buf).unwrap();

    let bytes = writer.inner.lock().unwrap();
    let s = String::from_utf8_lossy(bytes.get_ref());
    assert!(s.contains("write \"echo Hello World\""));
    // We use contains and not direct comparision because the actuall output depends on the shell.
    assert!(s.contains("read"));
}

Hide `Regex` needle by feature and enable it by default

I think for people who don't uses Regex, it could be good to not include it as a dependency to decrease a crate size.

[features]
default = ["regex"]
regex = ["regex"]

So it could be used like this.

expectrl = { version = "0.0.1", default-features = false }

Testing on different platforms

Even though it was tested on Windows and OSX.
Running a test suite may help to spot some issues.

For example on my machine on widows it passes all tests.
But on CI it fails everything 😞 I don't know why.
That's why windows CI is turned off currently.

4d05857
5ddc3f0

So yes it would be cool to be confident that everything is OK.

  • Linux
  • BSD(FreeBSD, OpenBSD, ...)
  • OSX
  • Windows
  • Solaris

repl::ReplSession::new function is private

Thanks for the wonderful library.
I want to create my custom ReplSession, but ReplSession::new function is private. So how can I create ReplSession from some sessions outside of this library?

I want to run program following:

let session = expectrl::repl::ReplSession::new(
    expectrl::spawn("mycommand").unwrap(),
    "EXPECT_PROMPT".to_string(),
    None, false);
let recv = session.execute("send");

Make async layer independent.

Currently we are using async_io for handling async feature.
And it currently works.

But it could be cool to be able to switch async primitive seamlessly.

Like use a smol::Unblock which runs async stuff in a separate thread.

Interact() shows passwords

Programs that ask for passwords don't repeat them as they're typed. For example,

$ sudo ls
Password:

but doesn't show the characters typed by the user. Here's a program using expectrs to do the same:

use expectrl::Session;
use std::process::Command;

fn main() {
    let mut cmd = Command::new("sudo");
    cmd.arg("ls");
    let mut session = Session::spawn_cmd(cmd).expect("Error while spawning");
    let status = session.interact().expect("Failed to start interact");
    println!("Exit status {:?}", status);
}

But it repeats whatever is typed.

Running `target/debug/expectrl_test`
Password: sdf

In my bash shell, the Password: prompt is actually followed by a key symbol, which seems to be a property of bash, not sudo.The same effect is seen with read -s, which I believe is a bash built-in and might be useful for testing.

Interact on Mac doesn't print what's being typed

Hi,
I'm trying to use expectrl to interact with a ssh password prompt on macOS (vers 12 if it matters)
everything works but after the ssh session gets initialized the user input is not "printed" on the command line.
Input is being passed to the ssh session, but it is invisible to the user.
Example:

netadmin@CO-90062106: ______________<--- command is not visible

Desktop/
Documents/
Downloads/
Library/
Movies/
Music/
Pictures/
Public/

But it produces the expected return when return is pressed.

This code reproduces the issue

use expectrl::{check, spawn, stream::stdin::Stdin, Error};
use std::io::{stdout, Write};

fn main() {
    let command = format!("ssh netadmin@localhost");
    let password = "123xxx";
    let mut sh = spawn("bash").expect(&format!("Unknown command: {:?}", command));
    writeln!(sh, "{}", command).unwrap();

    println!("Now you're in interacting mode");
    println!("To return control back to main type CTRL-] combination");
    let mut stdin = Stdin::open().expect("Failed to create stdin");

    sh.expect("Password:").unwrap();
    sh.send_line(password).unwrap();

    sh.interact(&mut stdin, stdout())
        .spawn()
        .expect("Failed to start interact");

    stdin.close().expect("Failed to close a stdin");

    println!("we keep dong things");
}

Strip bash decoded sequences.

If you take a look what we print here.

cargo run --example bash

You will find a list of sequences which aren't expected at all ...

println!("Current hostname: {:?}", hostname); // it prints some undetermined characters before hostname ...

Doesn't work on Windows in GitHub Actions

Consider the following very simple use of expectrl:

use expectrl::{spawn, Error};
use std::time::Duration;

fn main() -> Result<(), Error> {
    println!("Spawning ...");
    let mut p = spawn("cat")?;
    p.set_expect_timeout(Some(Duration::from_secs(3)));
    println!("Sending line ...");
    p.send_line("Hello World")?;
    println!("Expecting line ...");
    p.expect("Hello World")?;
    println!("OK!");
    Ok(())
}

If this program is run in GitHub Actions (using expectrl 0.6.0 and Rust 1.65.0), it succeeds on Ubuntu and macOS but fails on Windows; you can see such a run here. In particular, the output on Windows is:

Spawning ...
Sending line ...
Expecting line ...
Error: ExpectTimeout

Need non-blocking reads for interactive()

I can't use expect() directly to read lines for line processing because I need non-blocking reads. Using non-blocking reads allows more control while waiting for data to be available. For example, I add an animated cursor if the data takes too long to appear.

Here's the current code, roughly. It's loosely based on the expectrl source code using try_read here.

The following code shows a number of useful expectrl features. Specifically, it:

  • Catches \r and \n as the command writes lines, allowing them to be rewritten. This is useful because some commands emit just \r to overwrite the previous line in a terminal rather than scrolling.
  • Allows lines to be read individually and processed.
  • Allows timeouts to be checked.
  • Allows other processing in the loop, such as starting an animated cursor or waiting message if the command is taking too long to emit any output.
    use expectrl::{Session, Signal, WaitStatus};
    use std::io::{stdout, Write};
    use std::{process::Command, thread, time::Duration, time::SystemTime};

    fn main() {
      let mut cmd = Command::new("ping");
      cmd.args(&["www.time.org"]);

      let mut session = Session::spawn(cmd).expect("failed to execute process");
      thread::sleep(Duration::from_millis(300)); // Give the process a moment to start.

      let mut buf = vec![0; 512]; // Line buffer.
      let mut b = [0; 1]; // Read buffer.
      let sys_time = SystemTime::now();
      loop {
        match session.try_read(&mut b) {
          Ok(0) => {
            println!("==> EoF");
            break; // EoF
          }
          Ok(_) => {
            buf.push(b[0]);
            if b[0] == 10 || b[0] == 13 {
              stdout().write_all(&buf).unwrap();
              stdout().flush().unwrap();

              // (1) Further process the line as needed. 
              // ...

              buf.clear();
            }
          }
          Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {}
          Err(err) => {
            println!("err {}", err);
            break;
          }
        }
        // Check for timeout.
        // Start/update animated cursor if there's no output
      }

      match session.wait() {
        Ok(WaitStatus::Exited(pid, status)) => {
          println!("child process {} exited with: {:?}", pid, status);
        }
        // Signaled() is expected if the process is terminated.
        Ok(WaitStatus::Signaled(_, s, _)) => {
          println!("child process terminated with signal: {}", s);
        }
        Err(e) => {
          println!("error waiting for process: {:?}", e);
        }
        s => {
          println!("unhandled WaitStatus: {:?}", s);
        }
      }
    }

This code works because Session provides a try_read() that doesn't block. Currently,expect() already provides many of these features, including timeouts and the Any() method to detect specific line endings. But expect() blocks. With expect() blocking, I would have to multithread cursor animation and synchronize terminal writes. Doable, but it adds complexity that the try_read() approach avoids.

The problem:
How to allow interactive typing into the terminal, with the line processing as above? There's interactive()but it can't be used in this context because it blocks.

Possible solutions:

  1. Could there be a version of interactive() that emits lines for processing?
  2. Could there be a version of interactive() that accepts a passed-in function for line processing or other work? (eg, at (1) above).
  3. Could there be a try_interactive() allows interactivity, but checks for data written to stdout without blocking?

Session::spawn() method is hard to use across platforms

As things are now, Session::spawn() expects std::process::Command on Unix-like platforms and conpty::ProcAttr on Windows (the documentation only mentions the former). The API doesn’t expose the command type in use, this type is private. As a result, my code has to depend on conpty even though it doesn’t actually use it.

The way I see it, expectrl could at least expose the command type publicly. But ideally it would really take the standard std::process::Command structure on all platforms and convert to conpty::ProcAttr itself.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.