# HG changeset patch # User Antoine Cezar # Date 1591340495 -7200 # Node ID 513b3ef277a39dd758a328d1fe5c15f4e1700318 # Parent dd3050227a84a91fd87c0d513292222c43b9daf0 rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root` The println macro is not used to avoid string usage. Dealing only with bytes allows us to be compatible with any encoding and not just UTF8. Later on, format macro will be made to have more readable code. Differential Revision: https://phab.mercurial-scm.org/D8612 diff -r dd3050227a84 -r 513b3ef277a3 rust/Cargo.lock --- a/rust/Cargo.lock Mon Jul 06 22:02:50 2020 -0400 +++ b/rust/Cargo.lock Fri Jun 05 09:01:35 2020 +0200 @@ -489,6 +489,9 @@ [[package]] name = "rhg" version = "0.1.0" +dependencies = [ + "hg-core 0.1.0", +] [[package]] name = "rustc_version" diff -r dd3050227a84 -r 513b3ef277a3 rust/rhg/Cargo.toml --- a/rust/rhg/Cargo.toml Mon Jul 06 22:02:50 2020 -0400 +++ b/rust/rhg/Cargo.toml Fri Jun 05 09:01:35 2020 +0200 @@ -5,4 +5,5 @@ edition = "2018" [dependencies] +hg-core = { path = "../hg-core"} diff -r dd3050227a84 -r 513b3ef277a3 rust/rhg/src/commands.rs --- a/rust/rhg/src/commands.rs Mon Jul 06 22:02:50 2020 -0400 +++ b/rust/rhg/src/commands.rs Fri Jun 05 09:01:35 2020 +0200 @@ -1,3 +1,4 @@ +pub mod root; use crate::error::CommandError; /// The common trait for rhg commands diff -r dd3050227a84 -r 513b3ef277a3 rust/rhg/src/commands/root.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/rhg/src/commands/root.rs Fri Jun 05 09:01:35 2020 +0200 @@ -0,0 +1,76 @@ +use crate::commands::Command; +use crate::error::{CommandError, CommandErrorKind}; +use crate::ui::Ui; +use hg::operations::{FindRoot, FindRootError, FindRootErrorKind, Operation}; +use hg::utils::files::get_bytes_from_path; +use std::path::PathBuf; + +pub const HELP_TEXT: &str = " +Print the root directory of the current repository. + +Returns 0 on success. +"; + +pub struct RootCommand { + ui: Ui, +} + +impl RootCommand { + pub fn new() -> Self { + RootCommand { ui: Ui::new() } + } + + fn display_found_path( + &self, + path_buf: PathBuf, + ) -> Result<(), CommandError> { + let bytes = get_bytes_from_path(path_buf); + + // TODO use formating macro + self.ui.write_stdout(&[bytes.as_slice(), b"\n"].concat())?; + + Err(CommandErrorKind::Ok.into()) + } + + fn display_error(&self, error: FindRootError) -> Result<(), CommandError> { + match error.kind { + FindRootErrorKind::RootNotFound(path) => { + let bytes = get_bytes_from_path(path); + + // TODO use formating macro + self.ui.write_stderr( + &[ + b"abort: no repository found in '", + bytes.as_slice(), + b"' (.hg not found)!\n", + ] + .concat(), + )?; + + Err(CommandErrorKind::RootNotFound.into()) + } + FindRootErrorKind::GetCurrentDirError(e) => { + // TODO use formating macro + self.ui.write_stderr( + &[ + b"abort: error getting current working directory: ", + e.to_string().as_bytes(), + b"\n", + ] + .concat(), + )?; + + Err(CommandErrorKind::CurrentDirNotFound.into()) + } + } + } +} + +impl Command for RootCommand { + fn run(&self) -> Result<(), CommandError> { + match FindRoot::new().run() { + Ok(path_buf) => self.display_found_path(path_buf), + Err(e) => self.display_error(e), + } + } +} diff -r dd3050227a84 -r 513b3ef277a3 rust/rhg/src/error.rs --- a/rust/rhg/src/error.rs Mon Jul 06 22:02:50 2020 -0400 +++ b/rust/rhg/src/error.rs Fri Jun 05 09:01:35 2020 +0200 @@ -1,3 +1,60 @@ +use crate::exitcode; +use crate::ui::UiError; +use std::convert::From; + +/// The kind of command error +#[derive(Debug, PartialEq)] +pub enum CommandErrorKind { + /// The command finished without error + Ok, + /// The root of the repository cannot be found + RootNotFound, + /// The current directory cannot be found + CurrentDirNotFound, + /// The standard output stream cannot be written to + StdoutError, + /// The standard error stream cannot be written to + StderrError, +} + +impl CommandErrorKind { + pub fn get_exit_code(&self) -> exitcode::ExitCode { + match self { + CommandErrorKind::Ok => exitcode::OK, + CommandErrorKind::RootNotFound => exitcode::ABORT, + CommandErrorKind::CurrentDirNotFound => exitcode::ABORT, + CommandErrorKind::StdoutError => exitcode::ABORT, + CommandErrorKind::StderrError => exitcode::ABORT, + } + } +} + /// The error type for the Command trait #[derive(Debug, PartialEq)] -pub struct CommandError {} +pub struct CommandError { + pub kind: CommandErrorKind, +} + +impl CommandError { + /// Exist the process with the corresponding exit code. + pub fn exit(&self) -> () { + std::process::exit(self.kind.get_exit_code()) + } +} + +impl From for CommandError { + fn from(kind: CommandErrorKind) -> Self { + CommandError { kind } + } +} + +impl From for CommandError { + fn from(error: UiError) -> Self { + CommandError { + kind: match error { + UiError::StdoutError(_) => CommandErrorKind::StdoutError, + UiError::StderrError(_) => CommandErrorKind::StderrError, + }, + } + } +} diff -r dd3050227a84 -r 513b3ef277a3 rust/rhg/src/exitcode.rs --- a/rust/rhg/src/exitcode.rs Mon Jul 06 22:02:50 2020 -0400 +++ b/rust/rhg/src/exitcode.rs Fri Jun 05 09:01:35 2020 +0200 @@ -1,4 +1,10 @@ pub type ExitCode = i32; +/// Successful exit +pub const OK: ExitCode = 0; + +/// Generic abort +pub const ABORT: ExitCode = 255; + /// Command not implemented by rhg pub const UNIMPLEMENTED_COMMAND: ExitCode = 252; diff -r dd3050227a84 -r 513b3ef277a3 rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs Mon Jul 06 22:02:50 2020 -0400 +++ b/rust/rhg/src/main.rs Fri Jun 05 09:01:35 2020 +0200 @@ -1,6 +1,7 @@ mod commands; mod error; mod exitcode; +mod ui; fn main() { std::process::exit(exitcode::UNIMPLEMENTED_COMMAND) diff -r dd3050227a84 -r 513b3ef277a3 rust/rhg/src/ui.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/rhg/src/ui.rs Fri Jun 05 09:01:35 2020 +0200 @@ -0,0 +1,54 @@ +use std::io; +use std::io::Write; + +pub struct Ui {} + +/// The kind of user interface error +pub enum UiError { + /// The standard output stream cannot be written to + StdoutError(io::Error), + /// The standard error stream cannot be written to + StderrError(io::Error), +} + +/// The commandline user interface +impl Ui { + pub fn new() -> Self { + Ui {} + } + + /// Write bytes to stdout + pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> { + let mut stdout = io::stdout(); + + self.write_stream(&mut stdout, bytes) + .or_else(|e| self.into_stdout_error(e))?; + + stdout.flush().or_else(|e| self.into_stdout_error(e)) + } + + fn into_stdout_error(&self, error: io::Error) -> Result<(), UiError> { + self.write_stderr( + &[b"abort: ", error.to_string().as_bytes(), b"\n"].concat(), + )?; + Err(UiError::StdoutError(error)) + } + + /// Write bytes to stderr + pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> { + let mut stderr = io::stderr(); + + self.write_stream(&mut stderr, bytes) + .or_else(|e| Err(UiError::StderrError(e)))?; + + stderr.flush().or_else(|e| Err(UiError::StderrError(e))) + } + + fn write_stream( + &self, + stream: &mut impl Write, + bytes: &[u8], + ) -> Result<(), io::Error> { + stream.write_all(bytes) + } +}