Mercurial > public > mercurial-scm > hg
view rust/chg/src/message.rs @ 39974:a9c5fc436fd5
rust-chg: add callback to handle pager and shell command requests
This could be inlined into the ChgRunCommand state to be introduced by
the next patch, but it seemed good to separate any user interactions from
the IPC code.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Mon, 24 Sep 2018 18:18:35 +0900 |
parents | b1d8acd82d60 |
children | ce088b38f92b |
line wrap: on
line source
// Copyright 2018 Yuya Nishihara <yuya@tcha.org> // // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. //! Utility for parsing and building command-server messages. use bytes::Bytes; use std::error; use std::ffi::{OsStr, OsString}; use std::io; use std::os::unix::ffi::OsStrExt; pub use tokio_hglib::message::*; // re-exports /// Shell command type requested by the server. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CommandType { /// Pager should be spawned. Pager, /// Shell command should be executed to send back the result code. System, } /// Shell command requested by the server. #[derive(Clone, Debug, Eq, PartialEq)] pub struct CommandSpec { pub command: OsString, pub current_dir: OsString, pub envs: Vec<(OsString, OsString)>, } /// Parses "S" channel request into command type and spec. pub fn parse_command_spec(data: Bytes) -> io::Result<(CommandType, CommandSpec)> { let mut split = data.split(|&c| c == b'\0'); let ctype = parse_command_type(split.next().ok_or(new_parse_error("missing type"))?)?; let command = split.next().ok_or(new_parse_error("missing command"))?; let current_dir = split.next().ok_or(new_parse_error("missing current dir"))?; let mut envs = Vec::new(); for l in split { let mut s = l.splitn(2, |&c| c == b'='); let k = s.next().unwrap(); let v = s.next().ok_or(new_parse_error("malformed env"))?; envs.push((OsStr::from_bytes(k).to_owned(), OsStr::from_bytes(v).to_owned())); } let spec = CommandSpec { command: OsStr::from_bytes(command).to_owned(), current_dir: OsStr::from_bytes(current_dir).to_owned(), envs: envs, }; Ok((ctype, spec)) } fn parse_command_type(value: &[u8]) -> io::Result<CommandType> { match value { b"pager" => Ok(CommandType::Pager), b"system" => Ok(CommandType::System), _ => Err(new_parse_error(format!("unknown command type: {}", decode_latin1(value)))), } } fn decode_latin1<S>(s: S) -> String where S: AsRef<[u8]>, { s.as_ref().iter().map(|&c| c as char).collect() } fn new_parse_error<E>(error: E) -> io::Error where E: Into<Box<error::Error + Send + Sync>>, { io::Error::new(io::ErrorKind::InvalidData, error) } #[cfg(test)] mod tests { use std::os::unix::ffi::OsStringExt; use super::*; #[test] fn parse_command_spec_good() { let src = [b"pager".as_ref(), b"less -FRX".as_ref(), b"/tmp".as_ref(), b"LANG=C".as_ref(), b"HGPLAIN=".as_ref()].join(&0); let spec = CommandSpec { command: os_string_from(b"less -FRX"), current_dir: os_string_from(b"/tmp"), envs: vec![(os_string_from(b"LANG"), os_string_from(b"C")), (os_string_from(b"HGPLAIN"), os_string_from(b""))], }; assert_eq!(parse_command_spec(Bytes::from(src)).unwrap(), (CommandType::Pager, spec)); } #[test] fn parse_command_spec_too_short() { assert!(parse_command_spec(Bytes::from_static(b"")).is_err()); assert!(parse_command_spec(Bytes::from_static(b"pager")).is_err()); assert!(parse_command_spec(Bytes::from_static(b"pager\0less")).is_err()); } #[test] fn parse_command_spec_malformed_env() { assert!(parse_command_spec(Bytes::from_static(b"pager\0less\0/tmp\0HOME")).is_err()); } #[test] fn parse_command_spec_unknown_type() { assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err()); } fn os_string_from(s: &[u8]) -> OsString { OsString::from_vec(s.to_vec()) } }