1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Client library for the Mercurial command server
//!
//! This crate provides a client interface to the Mercurial distributed
//! version control system (DVCS) in Rust, using Mercurial's [command
//! server][]. The command server is designed to allow tools to be built
//! around Mercurial repositories, without being tied into Mercurial's
//! internal API or licensing.
//!
//! [command server]: https://mercurial.selenic.com/wiki/CommandServer
//!
//! ## High-level API
//!
//! The [`cmdserver`](cmdserver/index.html) module provides a high-level
//! interface which manages spawning and communicating with a command
//! server instance:
//!
//! ```rust
//! use hglib::cmdserver::CommandServer;
//! let cmdserver = CommandServer::new().ok().expect("failed to start command server");
//! ```
//!
//! This high-level interface is largely unimplemented so far, but
//! builds on the raw API that is already functional.
//!
//! ## Raw API
//!
//! The lower-level API in the [`connection`](connection/index.html)
//! module allows you to run commands at the level of the command server
//! protocol. Assembling the command and reading the result
//! chunk-by-chunk is done manually.
//!
//! ```rust
//! # use std::io;
//! # use std::io::prelude::*;
//! use hglib::connection::Connection;
//! use hglib::Chunk;
//! let mut conn = Connection::new().ok().expect("failed to start command server");
//! let (capabilities, encoding) =
//!     conn.read_hello().ok().expect("failed to read server hello");
//!
//! let cmditer =
//!     conn.raw_command(vec![b"log", b"-l", b"5"])
//!         .ok().expect("failed to send raw command");
//! for chunk in cmditer {
//!     match chunk {
//!         Ok(Chunk::Output(s)) => { io::stdout().write(&s); },
//!         Ok(Chunk::Error(s)) => { io::stdout().write(&s); },
//!         Ok(Chunk::Result(r)) => println!("command exited with status: {}", r),
//!         Ok(_) => {},
//!         Err(e) => panic!("failed to read chunk: {}", e),
//!     }
//! }
//! ```

extern crate byteorder;

/// A type representing a "chunk" of data received from the command server.
#[derive(Debug)]
pub enum Chunk {
    /// Data received on the output channel (equivalent to stdout).
    Output(Vec<u8>),
    /// Data received on the error channel (equivalent to stderr).
    Error(Vec<u8>),
    /// Data received on the debug channel (log entries).
    Debug(Vec<u8>),
    /// The exit code of a Mercurial command.
    Result(i32),
    /// Indicates that the client should send input of the given maximum length.
    Input(i32),
    /// Indicates that the client should send line-oriented input of the
    /// given maximum length.
    LineInput(i32),
}

pub mod connection;
pub mod cmdserver;

#[cfg(test)]
mod tests {
    use super::cmdserver::*;
    use super::Chunk;

    #[test]
    fn capabilities() {
        let cmdserver = CommandServer::new().unwrap();
        assert!(cmdserver.capabilities.len() > 0);
        assert!(cmdserver.capabilities.iter().any(|cap| cap == "runcommand"));
    }

    #[test]
    fn run_command() {
        let mut cmdserver = CommandServer::new().unwrap();
        let run = cmdserver.connection.raw_command(vec![b"summary"]).unwrap();
        let (mut result, mut output) = (-1i32, vec![]);
        for chunk in run {
            match chunk {
                Ok(Chunk::Output(s)) => output.extend(s),
                Ok(Chunk::Error(_)) => continue,
                Ok(Chunk::Result(r)) => result = r,
                Ok(c) => panic!("unexpected chunk: {:?}", c),
                Err(e) => panic!("failed to read command results: {}", e),
            }
        }
        assert!(output.starts_with(b"parent:"));
        assert_eq!(result, 0);
    }

    #[test]
    fn run_command_error() {
        let mut cmdserver = CommandServer::new().unwrap();
        let run = cmdserver.connection.raw_command(vec![b"noexist"]).unwrap();
        let (mut result, mut error) =
            (-1i32, vec![]);
        for chunk in run {
            match chunk {
                Ok(Chunk::Output(_)) => continue,
                Ok(Chunk::Error(s)) => error.extend(s),
                Ok(Chunk::Result(r)) => { result = r },
                Ok(c) => panic!("unexpected chunk: {:?}", c),
                Err(e) => panic!("failed to read command results: {}", e),
            }
        }
        assert_eq!(result, 255);
        assert!(error.starts_with(b"hg: unknown command 'noexist'"));
    }
}