extern crate byteorder;
extern crate subprocess;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::{Cursor, Read, Write};
use std::path::{Path, PathBuf};
use std::result::Result;
use subprocess::{Popen, PopenConfig, Redirection};
pub trait Runner {
fn runcommand<'a>(
&mut self,
args: &'a [&str],
prompt: Option<Box<dyn Prompt + 'a>>,
) -> Result<(Vec<u8>, i32), HglibError>;
}
pub trait Prompt {
fn call(&mut self, size: usize) -> &[u8];
}
#[derive(Debug)]
pub struct Client {
server: Popen,
encoding: String,
path: PathBuf,
}
pub struct Basic {}
#[derive(Debug)]
pub struct HglibError {
pub code: i32,
pub out: Option<Vec<u8>>,
msg: String,
}
impl HglibError {
pub(crate) fn handle_err(x: Result<(Vec<u8>, i32), HglibError>) -> Result<bool, HglibError> {
match x {
Ok((_, code)) => Ok(code == 0),
Err(err) => {
if err.code == 0 {
Ok(true)
} else if err.code == 1 {
Ok(false)
} else {
Err(err)
}
}
}
}
}
impl<T: std::string::ToString> From<T> for HglibError {
fn from(err: T) -> HglibError {
HglibError {
code: -1,
out: None,
msg: err.to_string(),
}
}
}
impl Drop for Client {
fn drop(&mut self) {
self.close().unwrap();
}
}
impl Client {
pub fn open<P: AsRef<Path>>(
path: P,
encoding: &str,
configs: &[&str],
) -> Result<Client, HglibError> {
let mut env: Vec<(OsString, OsString)> = env::vars_os().collect();
env.push((OsString::from("HGPLAIN"), OsString::from("1")));
if !encoding.is_empty() {
env.push((OsString::from("HGENCODING"), OsString::from(encoding)));
}
let path = path.as_ref().to_path_buf().canonicalize()?;
let path_str = path.to_str().unwrap();
let mut args = vec!["hg", "serve", "--cmdserver", "pipe", "-R", path_str];
for c in configs.iter() {
args.push("--config");
args.push(c);
}
let mut server = Popen::create(
&args,
PopenConfig {
stdout: Redirection::Pipe,
stdin: Redirection::Pipe,
stderr: Redirection::Pipe,
env: Some(env),
cwd: Some(OsString::from(path_str)),
..Default::default()
},
)?;
let encoding = Client::read_hello(&mut server)?;
let client = Client {
server,
encoding,
path,
};
Ok(client)
}
pub fn close(&mut self) -> Result<(), HglibError> {
self.server.terminate()?;
self.server.wait()?;
Ok(())
}
pub fn get_path(&self) -> &PathBuf {
&self.path
}
fn read_hello(server: &mut Popen) -> Result<String, HglibError> {
let stdout = server.stdout.as_mut().unwrap();
let mut chan: Vec<u8> = vec![0; 1];
let n = stdout.read(&mut chan)?;
if n != 1 || chan[0] != b'o' {
return Err("Cannot read hello".into());
}
let len = stdout.read_u32::<BigEndian>()? as usize;
let mut data: Vec<u8> = vec![0; len];
let n = stdout.read(&mut data)?;
if n != len {
return Err("Cannot read hello (invalid length)".into());
}
let out = std::str::from_utf8(&data)?;
let out: Vec<&str> = out.split('\n').collect();
if !out[0].contains("capabilities: ") {
return Err("Cannot read hello: no capabilities ".into());
}
if !out[1].contains("encoding: ") {
return Err("Cannot read hello: no encoding ".into());
}
Ok(out[1]["encoding: ".len()..].to_string())
}
fn read_data(
mut to_read: usize,
output: &mut Vec<u8>,
stdout: &mut File,
) -> Result<(), HglibError> {
let mut pos = output.len();
output.resize(pos + to_read, 0);
loop {
let n = stdout.read(&mut output[pos..])?;
if n == to_read {
break;
}
to_read -= n;
pos += n;
}
Ok(())
}
pub fn encoding(&self) -> &str {
&self.encoding
}
}
impl Runner for Client {
fn runcommand<'a>(
&mut self,
args: &'a [&str],
mut prompt: Option<Box<dyn Prompt + 'a>>,
) -> Result<(Vec<u8>, i32), HglibError> {
let mut stdin = self.server.stdin.as_mut().unwrap();
let args_size: usize = args.iter().map(|arg| -> usize { arg.len() }).sum();
let size = args_size + args.len() - 1;
writeln!(&mut stdin, "runcommand")?;
stdin.write_u32::<BigEndian>(size as u32)?;
if let Some((first, args)) = args.split_first() {
write!(&mut stdin, "{}", first)?;
for arg in args {
write!(&mut stdin, "\0{}", arg)?;
}
}
stdin.flush()?;
let stdout = self.server.stdout.as_mut().unwrap();
let mut out = Vec::<u8>::with_capacity(4096);
let mut chan: Vec<u8> = vec![0; 1];
loop {
let n = stdout.read(&mut chan)?;
if n != 1 {
return Err("Empty stdout".into());
}
let len = stdout.read_u32::<BigEndian>()? as usize;
match chan[0] {
b'e' => {
let mut err = Vec::<u8>::with_capacity(512);
Client::read_data(len, &mut err, stdout)?;
}
b'o' => {
Client::read_data(len, &mut out, stdout)?;
}
b'r' => {
let mut code: Vec<u8> = vec![0; len];
stdout.read_exact(&mut code)?;
let mut cur = Cursor::new(&code);
let code = cur.read_i32::<BigEndian>()?;
return if code != 0 {
Err(HglibError {
code,
out: Some(out.clone()),
msg: String::from_utf8(out).unwrap(),
})
} else {
Ok((out, code))
};
}
b'L' => {
if let Some(prompt) = prompt.as_mut() {
let buf = prompt.call(len);
stdin.write_u32::<BigEndian>(buf.len() as u32)?;
stdin.write_all(buf)?;
stdin.flush()?;
} else {
stdin.write_u32::<BigEndian>(0)?;
stdin.flush()?;
return Err("Hglib error: something is expected on stdin, please implement a Prompt".into());
}
}
_ => {
return Err(format!("Hglib error: invalid channel {}", chan[0] as char).into());
}
}
}
}
}
impl Runner for Basic {
fn runcommand<'a>(
&mut self,
args: &'a [&str],
_: Option<Box<dyn Prompt + 'a>>,
) -> Result<(Vec<u8>, i32), HglibError> {
let env: Vec<(OsString, OsString)> = env::vars_os().collect();
let mut command = Vec::with_capacity(args.len() + 1);
command.push("hg");
command.extend(args);
let mut process = Popen::create(
&command,
PopenConfig {
stdout: Redirection::Pipe,
cwd: Some(OsString::from(std::env::current_dir().unwrap())),
env: Some(env),
..Default::default()
},
)?;
process.wait()?;
Ok((Vec::new(), 0))
}
}