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.
marmoute | |
Alphare |
hg-reviewers |
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.
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
rust/rhg/src/commands/root.rs | ||
---|---|---|
25 | If stdout is closed, this will fails. We probably want a better error than that eventually. | |
35 | Maybe we use a bit more explicit error, like: "Error is RootNotFound, but no path provided" or soemthing in that flavor. | |
51 | Same feedback, there wil be legit cas were we cannot write to stdout and we should improve the errors here (and test theses case) | |
61 | What kind of error does Mercurial issue in this case ? Could we keep the same behavior ? |
Looks good. If I could nitpick some more, the description talks about WTF8, when it's UTF8 (because we're talking about String and not OsString).
Path | Packages | |||
---|---|---|---|---|
M | rust/Cargo.lock (3 lines) | |||
M | rust/rhg/Cargo.toml (1 line) | |||
M | rust/rhg/src/commands.rs (1 line) | |||
A | M | rust/rhg/src/commands/root.rs (76 lines) | ||
M | rust/rhg/src/error.rs (59 lines) | |||
M | rust/rhg/src/exitcode.rs (6 lines) | |||
M | rust/rhg/src/main.rs (1 line) | |||
A | M | rust/rhg/src/ui.rs (54 lines) |
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
[[package]] | [[package]] | ||||
name = "rhg" | name = "rhg" | ||||
version = "0.1.0" | version = "0.1.0" | ||||
dependencies = [ | |||||
"hg-core 0.1.0", | |||||
] | |||||
[[package]] | [[package]] | ||||
name = "rustc_version" | name = "rustc_version" | ||||
version = "0.2.3" | version = "0.2.3" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] |
[package] | [package] | ||||
name = "rhg" | name = "rhg" | ||||
version = "0.1.0" | version = "0.1.0" | ||||
authors = ["Antoine Cezar <antoine.cezar@octobus.net>"] | authors = ["Antoine Cezar <antoine.cezar@octobus.net>"] | ||||
edition = "2018" | edition = "2018" | ||||
[dependencies] | [dependencies] | ||||
hg-core = { path = "../hg-core"} | |||||
pub mod root; | |||||
use crate::error::CommandError; | use crate::error::CommandError; | ||||
/// The common trait for rhg commands | /// The common trait for rhg commands | ||||
/// | /// | ||||
/// Normalize the interface of the commands provided by rhg | /// Normalize the interface of the commands provided by rhg | ||||
pub trait Command { | pub trait Command { | ||||
fn run(&self) -> Result<(), CommandError>; | fn run(&self) -> Result<(), CommandError>; | ||||
} | } |
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 | /// The error type for the Command trait | ||||
#[derive(Debug, PartialEq)] | #[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<CommandErrorKind> for CommandError { | |||||
fn from(kind: CommandErrorKind) -> Self { | |||||
CommandError { kind } | |||||
} | |||||
} | |||||
impl From<UiError> for CommandError { | |||||
fn from(error: UiError) -> Self { | |||||
CommandError { | |||||
kind: match error { | |||||
UiError::StdoutError(_) => CommandErrorKind::StdoutError, | |||||
UiError::StderrError(_) => CommandErrorKind::StderrError, | |||||
}, | |||||
} | |||||
} | |||||
} |
pub type ExitCode = i32; | pub type ExitCode = i32; | ||||
/// Successful exit | |||||
pub const OK: ExitCode = 0; | |||||
/// Generic abort | |||||
pub const ABORT: ExitCode = 255; | |||||
/// Command not implemented by rhg | /// Command not implemented by rhg | ||||
pub const UNIMPLEMENTED_COMMAND: ExitCode = 252; | pub const UNIMPLEMENTED_COMMAND: ExitCode = 252; |
mod commands; | mod commands; | ||||
mod error; | mod error; | ||||
mod exitcode; | mod exitcode; | ||||
mod ui; | |||||
fn main() { | fn main() { | ||||
std::process::exit(exitcode::UNIMPLEMENTED_COMMAND) | std::process::exit(exitcode::UNIMPLEMENTED_COMMAND) | ||||
} | } |
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) | |||||
} | |||||
} |
If stdout is closed, this will fails. We probably want a better error than that eventually.